91 lines
7.1 KiB
PowerShell
91 lines
7.1 KiB
PowerShell
# 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'
|