Files
MapLibreParticleLab/tools/particle_android_pipeline/windows_build_testhost.ps1
OpenAI Codex 8d49d943f3
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
chore: add Android RN build pipeline
2026-09-13 21:31:28 +08:00

155 lines
9.3 KiB
PowerShell

[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$ManifestPath,
[string]$RepositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path,
[string]$MavenRepository = $(if ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.m2\repository' } else { '' }),
[switch]$DryRun,
[switch]$Install,
[switch]$Run
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Fail([string]$Message) { throw "PIPELINE_ERROR: $Message" }
function Get-Sha256([string]$Path) { return (Get-FileHash -Algorithm SHA256 -LiteralPath $Path).Hash.ToLowerInvariant() }
function Assert-FileContract($Entry, [string]$ManifestDirectory, [string]$Name) {
if ($null -eq $Entry -or [string]::IsNullOrWhiteSpace($Entry.relativePath) -or [string]::IsNullOrWhiteSpace($Entry.sha256)) {
Fail "manifest $Name contract is incomplete"
}
$candidate = Join-Path $ManifestDirectory $Entry.relativePath
if (-not (Test-Path -LiteralPath $candidate -PathType Leaf)) { Fail "manifest $Name is missing beside manifest: $candidate" }
$item = Get-Item -LiteralPath $candidate
if ([int64]$item.Length -ne [int64]$Entry.bytes) { Fail "manifest $Name byte count mismatch: $candidate" }
if ((Get-Sha256 $candidate) -ne $Entry.sha256.ToLowerInvariant()) { Fail "manifest $Name SHA-256 mismatch: $candidate" }
return $candidate
}
function Copy-MavenFile([string]$Source, [string]$Destination, [string]$ExpectedHash) {
if (Test-Path -LiteralPath $Destination -PathType Leaf) {
if ((Get-Sha256 $Destination) -ne $ExpectedHash.ToLowerInvariant()) {
Fail "refusing to overwrite different Maven Local file: $Destination"
}
Write-Host "PIPELINE: Maven Local already has verified file $Destination"
return
}
if ($DryRun) {
Write-Host "DRYRUN: Copy-Item $Source -> $Destination"
return
}
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $Destination) | Out-Null
Copy-Item -LiteralPath $Source -Destination $Destination
if ((Get-Sha256 $Destination) -ne $ExpectedHash.ToLowerInvariant()) { Fail "Maven Local copy SHA-256 mismatch: $Destination" }
}
if ($Install -and $DryRun) { Fail '-Install cannot be combined with -DryRun' }
if ($Run -and -not $Install) { Fail '-Run requires -Install' }
if (-not (Test-Path -LiteralPath $ManifestPath -PathType Leaf)) { Fail "manifest not found: $ManifestPath" }
if (-not (Test-Path -LiteralPath $RepositoryRoot -PathType Container)) { Fail "repository root not found: $RepositoryRoot" }
if ([string]::IsNullOrWhiteSpace($MavenRepository)) { Fail 'MavenRepository cannot be resolved; pass -MavenRepository explicitly' }
$manifestFullPath = (Resolve-Path -LiteralPath $ManifestPath).Path
$manifestDirectory = Split-Path -Parent $manifestFullPath
$manifest = Get-Content -Raw -LiteralPath $manifestFullPath | ConvertFrom-Json
if ($manifest.schemaVersion -ne 1) { Fail "unsupported manifest schemaVersion: $($manifest.schemaVersion)" }
if ([string]::IsNullOrWhiteSpace($manifest.gitHead) -or $manifest.gitHead -notmatch '^[0-9a-f]{40}$') { Fail 'manifest gitHead is invalid' }
if ($manifest.rnPackageVersion -ne $manifest.nativeVersion) { Fail "manifest version mismatch: RN=$($manifest.rnPackageVersion) Native=$($manifest.nativeVersion)" }
if ($manifest.nativeVersion -notmatch '^\d+\.\d+\.\d+-particle\.(?:[3-9]|[1-9]\d+)$') { Fail "manifest does not name particle.3 or a later explicit particle version: $($manifest.nativeVersion)" }
if ($manifest.mavenCoordinate -ne "cc.navsea.maplibre:android-sdk-particle-opengl:$($manifest.nativeVersion)") { Fail "unexpected Maven coordinate: $($manifest.mavenCoordinate)" }
if ($manifest.renderer -ne 'opengl') { Fail "unexpected renderer: $($manifest.renderer)" }
if (($manifest.abis -join ',') -ne 'armeabi-v7a,arm64-v8a,x86,x86_64') { Fail "unexpected ABI set: $($manifest.abis -join ',')" }
$aar = Assert-FileContract $manifest.aar $manifestDirectory 'aar'
$pom = Assert-FileContract $manifest.pom $manifestDirectory 'pom'
$module = Assert-FileContract $manifest.module $manifestDirectory 'module'
$tgz = Assert-FileContract $manifest.tgz $manifestDirectory 'tgz'
$hostPath = Join-Path $RepositoryRoot 'integration\react-native\MapLibreParticleRNTestHost'
$pluginPath = Join-Path $hostPath 'plugins\withParticleNativeAndroid.js'
$packageJsonPath = Join-Path $hostPath 'package.json'
if (-not (Test-Path -LiteralPath $hostPath -PathType Container)) { Fail "Test Host source is missing: $hostPath" }
if (-not (Test-Path -LiteralPath $pluginPath -PathType Leaf)) { Fail "Test Host plugin is missing: $pluginPath" }
if (-not (Test-Path -LiteralPath $packageJsonPath -PathType Leaf)) { Fail "Test Host package.json is missing: $packageJsonPath" }
$mavenVersionDir = Join-Path $MavenRepository "cc\navsea\maplibre\android-sdk-particle-opengl\$($manifest.nativeVersion)"
$mavenAar = Join-Path $mavenVersionDir ([IO.Path]::GetFileName($aar))
$mavenPom = Join-Path $mavenVersionDir ([IO.Path]::GetFileName($pom))
$mavenModule = Join-Path $mavenVersionDir ([IO.Path]::GetFileName($module))
$gradleCommand = ".\gradlew.bat :app:assembleDebug --console=plain"
Write-Host "PIPELINE: version=$($manifest.nativeVersion)"
Write-Host "PIPELINE: AAR=$aar"
Write-Host "PIPELINE: TGZ=$tgz"
Write-Host "PIPELINE: TestHost=$hostPath"
Write-Host "PIPELINE: MavenLocal=$mavenVersionDir"
Write-Host "PIPELINE: GradleCommand=$gradleCommand"
if ($DryRun) {
Write-Host "DRYRUN: would verify/update Maven Local, Test Host package dependency, native substitution, npm install, Expo prebuild, and Debug APK build"
exit 0
}
Copy-MavenFile $aar $mavenAar $manifest.aar.sha256
Copy-MavenFile $pom $mavenPom $manifest.pom.sha256
Copy-MavenFile $module $mavenModule $manifest.module.sha256
# Do not reset or clean the checkout. A dirty tracked Test Host is an ambiguous source input.
$relativeHostPath = 'integration/react-native/MapLibreParticleRNTestHost'
$hostStatus = & git -C $RepositoryRoot status --porcelain -- $relativeHostPath
if ($LASTEXITCODE -ne 0) { Fail 'unable to inspect Test Host Git status' }
if ($hostStatus) { Fail 'Test Host tracked sources are dirty; synchronize or commit them before pipeline consumption' }
& git -C $RepositoryRoot fetch origin particle/main
if ($LASTEXITCODE -ne 0) { Fail 'git fetch origin particle/main failed' }
& git -C $RepositoryRoot restore --source $manifest.gitHead --worktree -- $relativeHostPath
if ($LASTEXITCODE -ne 0) { Fail "unable to synchronize Test Host tracked sources at manifest gitHead $($manifest.gitHead)" }
$package = Get-Content -Raw -LiteralPath $packageJsonPath | ConvertFrom-Json
if ($null -eq $package.dependencies.'@navsea/maplibre-particle-rn') { Fail 'Test Host does not declare @navsea/maplibre-particle-rn dependency' }
$package.dependencies.'@navsea/maplibre-particle-rn' = "file:$tgz"
$package | ConvertTo-Json -Depth 20 | Set-Content -Encoding utf8NoBOM -LiteralPath $packageJsonPath
$plugin = Get-Content -Raw -LiteralPath $pluginPath
$updatedPlugin = [regex]::Replace($plugin, 'cc\.navsea\.maplibre:android-sdk-particle-opengl:[^"`\s]+', $manifest.mavenCoordinate)
if ($updatedPlugin -eq $plugin) { Fail 'Test Host native substitution coordinate was not found in plugin source' }
Set-Content -Encoding utf8NoBOM -LiteralPath $pluginPath -Value $updatedPlugin
Push-Location $hostPath
try {
& npm install --ignore-scripts --save-exact $tgz
if ($LASTEXITCODE -ne 0) { Fail 'npm install of manifest tarball failed' }
& npx expo prebuild --platform android
if ($LASTEXITCODE -ne 0) { Fail 'Expo Android prebuild failed' }
if (-not (Test-Path -LiteralPath 'android\gradlew.bat' -PathType Leaf)) { Fail 'Expo prebuild did not produce android\\gradlew.bat' }
# A non-clean prebuild preserves Windows local.properties. If Expo sees an existing
# marker it may retain an older generated substitution, so refresh only that coordinate.
$generatedBuildGradle = Join-Path $hostPath 'android\build.gradle'
if (-not (Test-Path -LiteralPath $generatedBuildGradle -PathType Leaf)) { Fail 'Expo prebuild did not produce android\\build.gradle' }
$generatedGradle = Get-Content -Raw -LiteralPath $generatedBuildGradle
$updatedGeneratedGradle = [regex]::Replace($generatedGradle, 'cc\.navsea\.maplibre:android-sdk-particle-opengl:[^"`\s]+', $manifest.mavenCoordinate)
if ($updatedGeneratedGradle -eq $generatedGradle) { Fail 'generated Android Maven substitution coordinate was not found' }
Set-Content -Encoding utf8NoBOM -LiteralPath $generatedBuildGradle -Value $updatedGeneratedGradle
Push-Location android
try {
& .\gradlew.bat :app:assembleDebug --console=plain
if ($LASTEXITCODE -ne 0) { Fail 'Debug Test Host APK build failed' }
} finally { Pop-Location }
} finally { Pop-Location }
$apk = Join-Path $hostPath 'android\app\build\outputs\apk\debug\app-debug.apk'
if (-not (Test-Path -LiteralPath $apk -PathType Leaf)) { Fail "expected Debug APK is missing: $apk" }
$apkItem = Get-Item -LiteralPath $apk
Write-Host "PIPELINE: APK=$apk bytes=$($apkItem.Length) sha256=$(Get-Sha256 $apk)"
if ($Install) {
& adb -s 192.168.1.146 install -r $apk
if ($LASTEXITCODE -ne 0) { Fail 'ADB install failed' }
if ($Run) {
& adb -s 192.168.1.146 shell am start -n org.maplibre.particle.rntesthost/.MainActivity
if ($LASTEXITCODE -ne 0) { Fail 'ADB activity start failed' }
}
}
Write-Host 'PIPELINE_PASS: Windows Test Host Debug APK build completed without default device actions'