Files
ucvl-zonghengjia-android/tools/Build-104-Candidate.ps1
T

91 lines
7.1 KiB
PowerShell
Raw Permalink Normal View History

# Local candidate only. Uses cached dependencies; never starts adb or an emulator.
param([string]$JobDirectory = '',[switch]$ResumeAfterBuild)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
$tempRoot = [IO.Path]::TrimEndingDirectorySeparator([IO.Path]::GetTempPath())
if ($ResumeAfterBuild -and -not $JobDirectory) { throw 'Resume requires the exact completed build directory' }
if (-not $JobDirectory) { $JobDirectory = Join-Path $tempRoot ('ucvl-android-104-' + [guid]::NewGuid().ToString('N')) }
$job = [IO.Path]::GetFullPath($JobDirectory)
if ([IO.Path]::GetDirectoryName($job) -ine $tempRoot -or [IO.Path]::GetFileName($job) -cnotmatch '^ucvl-android-104-[a-f0-9]{32}$' -or $job -match '[^\x00-\x7F]') {
throw 'A fresh scoped ASCII temporary directory is required'
}
if ((Test-Path -LiteralPath $job) -and -not $ResumeAfterBuild) { throw 'Candidate directory already exists' }
foreach ($name in @('universal','arm64-v8a','armeabi-v7a','x86','x86_64')) {
if (Test-Path -LiteralPath (Join-Path $repo "releases/UCVL-Zonghengjia-1.0.4-android-$name.apk")) { throw 'Signed 1.0.4 candidate already exists; do not replace it' }
}
$buildText = Get-Content -LiteralPath (Join-Path $repo 'V2rayNG/app/build.gradle.kts') -Raw
if ($buildText -notmatch 'versionCode = 10004' -or $buildText -notmatch 'versionName = "1.0.4"') { throw 'Source version mismatch' }
$project = Join-Path $job 'project'
$source = Join-Path $repo 'V2rayNG'
function Save-Json([string]$Name,$Value) {
[IO.File]::WriteAllText((Join-Path $job "evidence/$Name"),($Value | ConvertTo-Json -Depth 12),[Text.UTF8Encoding]::new($false))
}
$env:JAVA_HOME = Join-Path $repo '.tools/jdk-staging/jdk-21.0.12.1+1'
$env:ANDROID_HOME = Join-Path $repo '.tools/android-sdk'
if (-not $ResumeAfterBuild) {
$null = New-Item -ItemType Directory -Path $job,$project,(Join-Path $job 'evidence'),(Join-Path $job 'artifacts')
foreach ($name in @('gradlew','gradlew.bat','gradle.properties','settings.gradle.kts','build.gradle.kts','gradle')) {
Copy-Item -LiteralPath (Join-Path $source $name) -Destination $project -Recurse
}
$null = New-Item -ItemType Directory -Path (Join-Path $project 'app')
foreach ($name in @('src','libs','build.gradle.kts','proguard-rules.pro')) {
Copy-Item -LiteralPath (Join-Path $source "app/$name") -Destination (Join-Path $project 'app') -Recurse
}
$inputs = @(Get-ChildItem -LiteralPath $project -File -Recurse | Sort-Object FullName | ForEach-Object {
[ordered]@{Path=[IO.Path]::GetRelativePath($project,$_.FullName).Replace('\','/');Bytes=$_.Length;Sha256=(Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash}
})
Save-Json 'build-inputs.json' $inputs
$inputsHash = (Get-FileHash -LiteralPath (Join-Path $job 'evidence/build-inputs.json')).Hash
$started = [DateTimeOffset]::Now
Push-Location $project
try {
& ./gradlew.bat --offline --no-daemon --max-workers=2 :app:testPlaystoreReleaseUnitTest :app:assemblePlaystoreRelease *> (Join-Path $job 'evidence/gradle.log')
$buildExit = $LASTEXITCODE
} finally { Pop-Location }
$suites = @(Get-ChildItem -LiteralPath (Join-Path $project 'app/build/test-results/testPlaystoreReleaseUnitTest') -Filter 'TEST-*.xml' -File -ErrorAction SilentlyContinue | ForEach-Object {
[xml]$xml = Get-Content -LiteralPath $_.FullName -Raw
[pscustomobject]@{Name=$xml.testsuite.name;Tests=[int]$xml.testsuite.tests;Failures=[int]$xml.testsuite.failures;Errors=[int]$xml.testsuite.errors;Skipped=[int]$xml.testsuite.skipped}
})
$tests = [ordered]@{StartedAt=$started.ToString('O');ExitCode=$buildExit;Offline=$true;Suites=$suites;BuildInputsSha256=$inputsHash;PhysicalDeviceTested=$false;EmulatorStarted=$false;Published=$false}
Save-Json 'tests.json' $tests
} else {
$tests = Get-Content -LiteralPath (Join-Path $job 'evidence/tests.json') -Raw | ConvertFrom-Json
$suites = $tests.Suites
$buildExit = $tests.ExitCode
$inputs = Get-Content -LiteralPath (Join-Path $job 'evidence/build-inputs.json') -Raw | ConvertFrom-Json
$inputsHash = (Get-FileHash -LiteralPath (Join-Path $job 'evidence/build-inputs.json')).Hash
if ($inputsHash -ne $tests.BuildInputsSha256) { throw 'Completed build input manifest changed' }
}
if ($buildExit -ne 0) { throw "Gradle failed ($buildExit); evidence retained at $job" }
if (($suites | Measure-Object Tests -Sum).Sum -ne 41 -or ($suites | Measure-Object Failures -Sum).Sum -ne 0 -or ($suites | Measure-Object Errors -Sum).Sum -ne 0 -or ($suites | Measure-Object Skipped -Sum).Sum -ne 0) {
throw 'Unexpected unit test coverage or failures'
}
foreach ($row in $inputs) {
if ((Get-FileHash -LiteralPath (Join-Path $project $row.Path)).Hash -ne $row.Sha256 -or (Get-FileHash -LiteralPath (Join-Path $source $row.Path)).Hash -ne $row.Sha256) {
throw "Input changed during build: $($row.Path)"
}
}
& (Join-Path $repo 'Sign-Candidate.ps1') -Version '1.0.4' -InputDirectory (Join-Path $project 'app/build/outputs/apk/playstore/release') *> (Join-Path $job 'evidence/signing.log')
$java = Join-Path $env:JAVA_HOME 'bin/java.exe'
$signer = Join-Path $env:ANDROID_HOME 'build-tools/37.0.0/lib/apksigner.jar'
$aapt = Join-Path $env:ANDROID_HOME 'build-tools/37.0.0/aapt.exe'
$expectedCertificate = '0f1a09870d6ee1e73b62138f99463e7066f4228ce28b7f16ff0aeccca59ea14f'
foreach ($version in @('1.0.2','1.0.3')) {
$verification = (& $java -jar $signer verify --verbose --print-certs (Join-Path $repo "releases/UCVL-Zonghengjia-$version-android-universal.apk") 2>&1 | Out-String)
if ($LASTEXITCODE -ne 0 -or $verification -notmatch $expectedCertificate) { throw "Old $version signing identity differs" }
}
$packages = @(foreach ($file in Get-ChildItem -LiteralPath (Join-Path $repo 'releases') -Filter 'UCVL-Zonghengjia-1.0.4-android-*.apk' -File | Sort-Object Name) {
$verification = (& $java -jar $signer verify --verbose --print-certs $file.FullName 2>&1 | Out-String)
if ($LASTEXITCODE -ne 0 -or $verification -notmatch $expectedCertificate -or $verification -notmatch 'v2\): true' -or $verification -notmatch 'v3\): true') { throw "Candidate signature mismatch: $($file.Name)" }
$copy = Join-Path $job "artifacts/$($file.Name)"
Copy-Item -LiteralPath $file.FullName -Destination $copy
$badging = (& $aapt dump badging $copy 2>&1 | Out-String)
if ($LASTEXITCODE -ne 0 -or $badging -notmatch "package: name='cn.toplc.zonghengjia' versionCode='4010004' versionName='1.0.4'") { throw "Candidate manifest mismatch: $($file.Name)" }
[ordered]@{Name=$file.Name;Bytes=$file.Length;Sha256=(Get-FileHash -LiteralPath $copy).Hash;Version='1.0.4';VersionCode=4010004;SignerSha256=$expectedCertificate;V2=$true;V3=$true}
})
if ($packages.Count -ne 5) { throw 'Expected all five ABI/universal packages' }
Save-Json 'candidate.json' ([ordered]@{Version='1.0.4';VersionCode=4010004;BaseVersionCode=10004;JobDirectory=$job;BuiltAt=[DateTimeOffset]::Now.ToString('O');Packages=$packages;BuildInputsSha256=$inputsHash;UnitTests=41;AuthorizationLifecycleJvmTests=10;SameSignerAs=@('1.0.2','1.0.3');PhysicalDeviceTested=$false;AndroidServiceBroadcastTested=$false;StableReleaseAccepted=$false;Published=$false})
Write-Output "CANDIDATE_JOB=$job"
Write-Output 'CANDIDATE_BUILD_SIGN_VERIFY_PASSED'