[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'