Files
Cognitive_Agent/N7Cockpit/scripts/build-migrated-apk.ps1
T

193 lines
6.8 KiB
PowerShell

[CmdletBinding()]
param(
[string]$ConfigPath = '',
[ValidateSet('debug', 'release')]
[string]$Variant = 'debug',
[switch]$AllowHardware
)
$ErrorActionPreference = 'Stop'
$projectRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
if (-not $ConfigPath) {
$ConfigPath = Join-Path $projectRoot 'config\migration.psd1'
}
if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
$ConfigPath = Join-Path $projectRoot $ConfigPath
}
if (-not (Test-Path -LiteralPath $ConfigPath)) {
throw "Migration config does not exist: $ConfigPath"
}
$config = Import-PowerShellDataFile -LiteralPath $ConfigPath
$required = @(
'ServerAUrl',
'AndroidServerAUrl',
'VehicleId',
'HardwareClientId',
'ExecutionTarget',
'TokenFile'
)
foreach ($key in $required) {
if (-not $config.ContainsKey($key) -or -not ([string]$config[$key]).Trim()) {
throw "Migration config is missing: $key"
}
}
$serverAUrl = ([string]$config.ServerAUrl).TrimEnd('/')
$androidServerAUrl = ([string]$config.AndroidServerAUrl).TrimEnd('/')
$vehicleId = ([string]$config.VehicleId).Trim()
$hardwareClientId = ([string]$config.HardwareClientId).Trim()
$executionTarget = ([string]$config.ExecutionTarget).Trim().ToLowerInvariant()
if ($executionTarget -notin @('dry_run', 'simulated_hardware', 'hardware')) {
throw "Unsupported ExecutionTarget: $executionTarget"
}
if ($executionTarget -eq 'hardware' -and -not $AllowHardware) {
throw 'Safety interlock: hardware builds require the explicit -AllowHardware switch.'
}
$tokenPath = [string]$config.TokenFile
if (-not [System.IO.Path]::IsPathRooted($tokenPath)) {
$tokenPath = Join-Path $projectRoot $tokenPath
}
if (-not (Test-Path -LiteralPath $tokenPath)) {
throw "Device token file does not exist: $tokenPath"
}
$deviceToken = (Get-Content -LiteralPath $tokenPath -Raw).Trim()
if ($deviceToken.Length -lt 32) {
throw 'The device token must contain at least 32 characters.'
}
$javaHomeCandidates = @(@(
[string]$config.JavaHome,
$env:JAVA_HOME,
'C:\Program Files\Android\Android Studio\jbr'
) | Where-Object { $_ -and (Test-Path -LiteralPath $_) })
if (-not $javaHomeCandidates) {
throw 'JDK 17 was not found. Set JavaHome in config or JAVA_HOME.'
}
$javaHome = $javaHomeCandidates[0]
$javaExe = Join-Path $javaHome 'bin\java.exe'
if (-not (Test-Path -LiteralPath $javaExe)) {
throw "java.exe does not exist under JavaHome: $javaHome"
}
$savedErrorAction = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
$javaVersion = (& $javaExe -version 2>&1 | Out-String)
$ErrorActionPreference = $savedErrorAction
$javaMajor = if ($javaVersion -match 'version "(?<major>\d+)') {
[int]$Matches.major
} else {
0
}
if ($javaMajor -lt 17) {
throw "JDK 17 or newer is required. Detected: $($javaVersion.Trim())"
}
$sdkCandidates = @(@(
[string]$config.AndroidSdk,
$env:ANDROID_SDK_ROOT,
$env:ANDROID_HOME,
(Join-Path $env:LOCALAPPDATA 'Android\Sdk')
) | Where-Object { $_ -and (Test-Path -LiteralPath $_) })
if (-not $sdkCandidates) {
throw 'Android SDK was not found. Set AndroidSdk, ANDROID_SDK_ROOT, or ANDROID_HOME.'
}
$androidSdk = $sdkCandidates[0]
if (-not (Test-Path -LiteralPath (Join-Path $androidSdk 'platforms\android-35'))) {
throw "Android SDK platform 35 is missing under: $androidSdk"
}
$headers = @{ Authorization = "Bearer $deviceToken" }
$catalogUri = "$serverAUrl/api/v1/vehicles/$vehicleId/modes"
try {
$catalog = Invoke-RestMethod -Uri $catalogUri -Headers $headers -TimeoutSec 10
} catch {
throw "Server A preflight failed at $catalogUri : $($_.Exception.Message)"
}
$presetCount = @($catalog.modes | Where-Object { $_.origin -eq 'preset' }).Count
if ($presetCount -lt 1) {
throw 'Server A returned no repository preset modes.'
}
$oldJavaHome = $env:JAVA_HOME
$oldAndroidSdkRoot = $env:ANDROID_SDK_ROOT
$oldAndroidHome = $env:ANDROID_HOME
$oldBaseUrl = $env:COGNITIVE_AGENT_BASE_URL
$oldDeviceToken = $env:COGNITIVE_AGENT_DEVICE_API_TOKEN
try {
$env:JAVA_HOME = $javaHome
$env:ANDROID_SDK_ROOT = $androidSdk
$env:ANDROID_HOME = $androidSdk
$env:COGNITIVE_AGENT_BASE_URL = $androidServerAUrl
$env:COGNITIVE_AGENT_DEVICE_API_TOKEN = $deviceToken
$gradle = ([string]$config.GradleExecutable).Trim()
if (-not $gradle) { $gradle = Join-Path $projectRoot 'gradlew.bat' }
if (-not [System.IO.Path]::IsPathRooted($gradle)) {
$gradle = Join-Path $projectRoot $gradle
}
if (-not (Test-Path -LiteralPath $gradle)) {
throw "Gradle executable does not exist: $gradle"
}
$variantTask = if ($Variant -eq 'release') { 'assembleRelease' } else { 'assembleDebug' }
$testTask = if ($Variant -eq 'release') { $null } else { 'testDebugUnitTest' }
$arguments = @(
'--no-daemon',
"-PorchestratorVehicleId=$vehicleId",
"-PorchestratorHardwareClientId=$hardwareClientId",
"-PorchestratorExecutionTarget=$executionTarget"
)
if ($testTask) { $arguments += $testTask }
$arguments += $variantTask
Push-Location $projectRoot
try {
& $gradle @arguments
if ($LASTEXITCODE -ne 0) { throw 'Gradle build failed.' }
} finally {
Pop-Location
}
} finally {
$env:JAVA_HOME = $oldJavaHome
$env:ANDROID_SDK_ROOT = $oldAndroidSdkRoot
$env:ANDROID_HOME = $oldAndroidHome
$env:COGNITIVE_AGENT_BASE_URL = $oldBaseUrl
$env:COGNITIVE_AGENT_DEVICE_API_TOKEN = $oldDeviceToken
$deviceToken = $null
}
$sourceApk = Join-Path $projectRoot "app\build\outputs\apk\$Variant\app-$Variant.apk"
if (-not (Test-Path -LiteralPath $sourceApk)) {
throw "Built APK was not found: $sourceApk"
}
$distDirectory = Join-Path $projectRoot 'dist'
New-Item -ItemType Directory -Force -Path $distDirectory | Out-Null
$destinationApk = Join-Path $distDirectory "N7Cockpit-remote-$Variant.apk"
Copy-Item -LiteralPath $sourceApk -Destination $destinationApk -Force
$apkHash = (Get-FileHash -LiteralPath $destinationApk -Algorithm SHA256).Hash
$buildInfo = [ordered]@{
generated_at = (Get-Date).ToUniversalTime().ToString('o')
variant = $Variant
package = if ($Variant -eq 'debug') {
'com.seeker.n7cockpit.remote'
} else {
'com.seeker.n7cockpit'
}
server_a_url_from_server_b = $serverAUrl
android_server_a_url = $androidServerAUrl
vehicle_id = $vehicleId
hardware_client_id = $hardwareClientId
execution_target = $executionTarget
preset_count = $presetCount
apk_sha256 = $apkHash
}
$buildInfo | ConvertTo-Json | Set-Content -LiteralPath (
Join-Path $distDirectory 'build-info.json'
) -Encoding UTF8
Write-Host ''
Write-Host 'BUILD PASSED'
Write-Host "APK: $destinationApk"
Write-Host "SHA256: $apkHash"
Write-Host "Execution target: $executionTarget"
Write-Host 'The token is embedded in the APK. Keep the APK private and rotate the token if leaked.'