223 lines
8.4 KiB
PowerShell
Executable File
223 lines
8.4 KiB
PowerShell
Executable File
[CmdletBinding()]
|
|
param(
|
|
[ValidateSet('combined')]
|
|
[string]$Profile = 'combined',
|
|
[string]$Configuration = 'rev_d',
|
|
[string]$RepositoryRoot = '',
|
|
[string]$IarBuild = '',
|
|
[string]$OutputDirectory = '',
|
|
[ValidateSet(0, 1)]
|
|
[int]$PositionControl = 1,
|
|
[ValidateSet(0, 1)]
|
|
[int]$Ventilation = 1,
|
|
[ValidateSet(0, 1)]
|
|
[int]$Heating = 1,
|
|
[ValidateSet(0, 1)]
|
|
[int]$RockingHorse = $PositionControl
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
if ($RockingHorse -eq 1 -and $PositionControl -eq 0) {
|
|
throw 'Rocking-horse mode requires -PositionControl 1.'
|
|
}
|
|
$repositoryCandidate = if ($RepositoryRoot) {
|
|
$RepositoryRoot
|
|
} else {
|
|
Join-Path $PSScriptRoot '..'
|
|
}
|
|
$resolvedRepository = (Resolve-Path -LiteralPath $repositoryCandidate).Path
|
|
$firmwareRoot = Join-Path $resolvedRepository 'firmware\t2g'
|
|
$repositoryFirmware = Join-Path $firmwareRoot 'Seat_Controller_demo'
|
|
$repositoryProject = Join-Path $repositoryFirmware 'IAR\Seat_Controller_demo.ewp'
|
|
$vendorCommon = Join-Path $firmwareRoot 'common'
|
|
$vendorDevice = Join-Path $firmwareRoot 'tviibh8m'
|
|
|
|
function Resolve-IarBuild {
|
|
param([string]$ConfiguredPath)
|
|
|
|
$candidates = @()
|
|
if ($ConfiguredPath) {
|
|
$candidates += $ConfiguredPath
|
|
}
|
|
if ($env:COGNITIVE_AGENT_IAR_BUILD) {
|
|
$candidates += $env:COGNITIVE_AGENT_IAR_BUILD
|
|
}
|
|
$command = Get-Command iarbuild.exe -ErrorAction SilentlyContinue
|
|
if ($command) {
|
|
$candidates += $command.Source
|
|
}
|
|
$candidates += @(
|
|
'D:\Program Files\IAR Systems\Embedded Workbench 9.2\common\bin\iarbuild.exe',
|
|
'C:\Program Files\IAR Systems\Embedded Workbench 9.2\common\bin\iarbuild.exe'
|
|
)
|
|
foreach ($candidate in $candidates) {
|
|
if ($candidate -and (Test-Path -LiteralPath $candidate -PathType Leaf)) {
|
|
return (Resolve-Path -LiteralPath $candidate).Path
|
|
}
|
|
}
|
|
throw 'IAR iarbuild.exe was not found; pass -IarBuild or set COGNITIVE_AGENT_IAR_BUILD.'
|
|
}
|
|
|
|
foreach ($required in @($repositoryProject, $vendorCommon, $vendorDevice)) {
|
|
if (-not (Test-Path -LiteralPath $required)) {
|
|
throw "Self-contained firmware dependency is missing: $required"
|
|
}
|
|
}
|
|
$resolvedIarBuild = Resolve-IarBuild -ConfiguredPath $IarBuild
|
|
|
|
function Invoke-IarBuild {
|
|
param([string]$ProjectPath)
|
|
|
|
Write-Host "Building Seat_Controller_demo ($Configuration)..."
|
|
& $resolvedIarBuild $ProjectPath -build $Configuration -log all
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "IAR build failed with exit code $LASTEXITCODE"
|
|
}
|
|
}
|
|
|
|
function Write-ImageSummary {
|
|
param([string]$ImagePath)
|
|
|
|
if (-not (Test-Path -LiteralPath $ImagePath -PathType Leaf)) {
|
|
throw "Build completed but HEX image was not found: $ImagePath"
|
|
}
|
|
# Use the .NET primitive directly. Some locked-down PowerShell hosts do not
|
|
# expose Get-FileHash even though the Utility module is otherwise present.
|
|
$stream = [System.IO.File]::OpenRead($ImagePath)
|
|
try {
|
|
$algorithm = [System.Security.Cryptography.SHA256]::Create()
|
|
try {
|
|
$hashBytes = $algorithm.ComputeHash($stream)
|
|
}
|
|
finally {
|
|
$algorithm.Dispose()
|
|
}
|
|
}
|
|
finally {
|
|
$stream.Dispose()
|
|
}
|
|
$hashValue = [System.BitConverter]::ToString($hashBytes).Replace('-', '').ToLowerInvariant()
|
|
Write-Host "Seat_Controller_demo HEX: $ImagePath"
|
|
Write-Host "Seat_Controller_demo SHA256: $hashValue"
|
|
}
|
|
|
|
function Copy-BuildArtifact {
|
|
param(
|
|
[string]$Source,
|
|
[string]$Destination
|
|
)
|
|
|
|
$lastError = $null
|
|
for ($attempt = 1; $attempt -le 6; $attempt++) {
|
|
try {
|
|
Copy-Item -LiteralPath $Source -Destination $Destination -Force
|
|
return
|
|
}
|
|
catch [System.IO.IOException] {
|
|
$lastError = $_
|
|
if ($attempt -lt 6) {
|
|
Start-Sleep -Milliseconds (150 * $attempt)
|
|
}
|
|
}
|
|
}
|
|
throw $lastError
|
|
}
|
|
|
|
# Preserve the IAR project's original ../../common and ../../tviibh8m layout
|
|
# while keeping both vendor trees inside this repository. Building in a
|
|
# temporary staging root avoids committing IAR Obj/BrowseInfo/List products.
|
|
$temporaryParent = Join-Path $resolvedRepository '.codex_tmp'
|
|
New-Item -ItemType Directory -Path $temporaryParent -Force | Out-Null
|
|
$stagingRoot = Join-Path $temporaryParent (
|
|
'seat-firmware-build-' + [guid]::NewGuid().ToString('N')
|
|
)
|
|
$stagingRootFull = [System.IO.Path]::GetFullPath($stagingRoot)
|
|
$temporaryParentFull = [System.IO.Path]::GetFullPath($temporaryParent)
|
|
if (-not $stagingRootFull.StartsWith(
|
|
$temporaryParentFull + [System.IO.Path]::DirectorySeparatorChar,
|
|
[System.StringComparison]::OrdinalIgnoreCase)) {
|
|
throw "Firmware staging directory escaped repository temporary root: $stagingRootFull"
|
|
}
|
|
|
|
try {
|
|
New-Item -ItemType Directory -Path $stagingRootFull | Out-Null
|
|
$stagingProjectRoot = Join-Path $stagingRootFull 'Seat_Controller_demo'
|
|
Copy-Item -LiteralPath $repositoryFirmware -Destination $stagingProjectRoot -Recurse
|
|
New-Item -ItemType Junction -Path (Join-Path $stagingRootFull 'common') -Target $vendorCommon | Out-Null
|
|
New-Item -ItemType Junction -Path (Join-Path $stagingRootFull 'tviibh8m') -Target $vendorDevice | Out-Null
|
|
|
|
$featureProfilePath = Join-Path $stagingProjectRoot 'Config\seat_feature_profile.h'
|
|
# Preserve dependency checks and derived switches (including THERMAL).
|
|
$featureProfile = [System.IO.File]::ReadAllText($featureProfilePath)
|
|
$featureValues = [ordered]@{
|
|
SEAT_FEATURE_POSITION_CONTROL = $PositionControl
|
|
SEAT_FEATURE_VENTILATION = $Ventilation
|
|
SEAT_FEATURE_HEATING = $Heating
|
|
SEAT_FEATURE_ROCKING_HORSE = $RockingHorse
|
|
}
|
|
foreach ($entry in $featureValues.GetEnumerator()) {
|
|
$pattern = '(?m)^#define\s+' + $entry.Key + '\s+\([01]\)'
|
|
if (-not [regex]::IsMatch($featureProfile, $pattern)) {
|
|
throw "Firmware feature switch is missing: $($entry.Key)"
|
|
}
|
|
$featureProfile = [regex]::Replace(
|
|
$featureProfile, $pattern, "#define $($entry.Key) ($($entry.Value))"
|
|
)
|
|
}
|
|
[System.IO.File]::WriteAllText(
|
|
$featureProfilePath,
|
|
$featureProfile,
|
|
[System.Text.UTF8Encoding]::new($false)
|
|
)
|
|
Write-Host "Feature profile: position=$PositionControl ventilation=$Ventilation heating=$Heating rocking_horse=$RockingHorse"
|
|
|
|
$stagedProject = Join-Path $stagingProjectRoot 'IAR\Seat_Controller_demo.ewp'
|
|
Invoke-IarBuild -ProjectPath $stagedProject
|
|
|
|
$stagedExe = Join-Path $stagingProjectRoot "IAR\$Configuration\Exe"
|
|
$artifactOutput = if ($OutputDirectory) {
|
|
[System.IO.Path]::GetFullPath($OutputDirectory)
|
|
} else {
|
|
Join-Path $repositoryFirmware "IAR\$Configuration\Exe"
|
|
}
|
|
New-Item -ItemType Directory -Path $artifactOutput -Force | Out-Null
|
|
Copy-BuildArtifact -Source $featureProfilePath -Destination (
|
|
Join-Path $artifactOutput 'seat_feature_profile.h'
|
|
)
|
|
foreach ($artifact in @('Seat_Controller_demo.hex', 'Seat_Controller_demo.out')) {
|
|
$sourceArtifact = Join-Path $stagedExe $artifact
|
|
if (-not (Test-Path -LiteralPath $sourceArtifact -PathType Leaf)) {
|
|
throw "IAR build did not produce $sourceArtifact"
|
|
}
|
|
Copy-BuildArtifact -Source $sourceArtifact -Destination (
|
|
Join-Path $artifactOutput $artifact
|
|
)
|
|
}
|
|
Write-ImageSummary -ImagePath (
|
|
Join-Path $artifactOutput 'Seat_Controller_demo.hex'
|
|
)
|
|
}
|
|
finally {
|
|
if (Test-Path -LiteralPath $stagingRootFull) {
|
|
foreach ($dependencyLink in @('common', 'tviibh8m')) {
|
|
$linkPath = Join-Path $stagingRootFull $dependencyLink
|
|
if (Test-Path -LiteralPath $linkPath) {
|
|
$link = Get-Item -LiteralPath $linkPath -Force
|
|
if (($link.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -eq 0) {
|
|
throw "Refusing to remove non-junction dependency path: $linkPath"
|
|
}
|
|
[System.IO.Directory]::Delete($linkPath, $false)
|
|
}
|
|
}
|
|
$remainingLinks = @(
|
|
Get-ChildItem -LiteralPath $stagingRootFull -Recurse -Force -Attributes ReparsePoint -ErrorAction SilentlyContinue
|
|
)
|
|
if ($remainingLinks.Count -ne 0) {
|
|
throw "Refusing recursive cleanup because a reparse point remains in $stagingRootFull"
|
|
}
|
|
[System.IO.Directory]::Delete($stagingRootFull, $true)
|
|
}
|
|
}
|
|
|
|
Write-Host 'Build complete. This script never flashes or starts a physical actuator.'
|