223 lines
7.7 KiB
PowerShell
223 lines
7.7 KiB
PowerShell
param(
|
|
[string]$Slug = 'ikar-android',
|
|
[string]$Name,
|
|
[string]$Summary = 'Android client build',
|
|
[string]$Description = 'Android release channel for the Ikar client.',
|
|
[string]$Version,
|
|
[string]$Channel = 'stable',
|
|
[string]$Notes,
|
|
[string]$RepositoryUrl,
|
|
[string]$HomepageUrl = 'https://ikar.kusoft.xyz',
|
|
[switch]$Unlisted,
|
|
[switch]$SkipBuild
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
function Require-Tool([string]$Path, [string]$Label) {
|
|
if (-not (Test-Path $Path)) {
|
|
throw "$Label was not found: $Path"
|
|
}
|
|
}
|
|
|
|
function Get-ApkBadgingValue([string]$BadgingText, [string]$Pattern) {
|
|
$match = [regex]::Match($BadgingText, $Pattern, [System.Text.RegularExpressions.RegexOptions]::Multiline)
|
|
if (-not $match.Success) {
|
|
return $null
|
|
}
|
|
|
|
return $match.Groups[1].Value
|
|
}
|
|
|
|
function Resolve-ArgusPlatform([string[]]$NativeCodes) {
|
|
$normalized = $NativeCodes | Where-Object { $_ } | ForEach-Object { $_.Trim().ToLowerInvariant() }
|
|
$allUniversalAbis = @('arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64')
|
|
if (@($allUniversalAbis | Where-Object { $normalized -contains $_ }).Count -eq $allUniversalAbis.Count) {
|
|
return 'android-universal'
|
|
}
|
|
|
|
if ($normalized -contains 'arm64-v8a') {
|
|
return 'android-arm64'
|
|
}
|
|
|
|
if ($normalized -contains 'armeabi-v7a') {
|
|
return 'android-armv7'
|
|
}
|
|
|
|
if ($normalized -contains 'x86_64') {
|
|
return 'android-x86_64'
|
|
}
|
|
|
|
if ($normalized -contains 'x86') {
|
|
return 'android-x86'
|
|
}
|
|
|
|
return 'android'
|
|
}
|
|
|
|
function Get-RepositoryUrl() {
|
|
$remote = git remote get-url origin 2>$null
|
|
if ($LASTEXITCODE -eq 0 -and -not [string]::IsNullOrWhiteSpace($remote)) {
|
|
return $remote.Trim()
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
$repoRoot = Split-Path -Parent $PSScriptRoot
|
|
Set-Location $repoRoot
|
|
|
|
$javaHome = if ($env:JAVA_HOME) { $env:JAVA_HOME } else { 'C:\Program Files\Android\openjdk\jdk-21.0.8' }
|
|
$androidHome = if ($env:ANDROID_HOME) { $env:ANDROID_HOME } else { 'C:\Users\seven\AppData\Local\Android\Sdk' }
|
|
$env:JAVA_HOME = $javaHome
|
|
$env:ANDROID_HOME = $androidHome
|
|
|
|
$projectPath = Join-Path $repoRoot 'src\Ikar.Client\Ikar.Client.csproj'
|
|
$publishDir = Join-Path $repoRoot 'src\Ikar.Client\bin\Release\net10.0-android'
|
|
$releaseApkPath = Join-Path $publishDir 'com.seven.ikar-Signed.apk'
|
|
$aaptPath = Join-Path $androidHome 'build-tools\36.1.0\aapt.exe'
|
|
$apksignerPath = Join-Path $androidHome 'build-tools\36.1.0\apksigner.bat'
|
|
|
|
Require-Tool $projectPath 'Ikar.Client project'
|
|
Require-Tool $aaptPath 'aapt'
|
|
Require-Tool $apksignerPath 'apksigner'
|
|
|
|
if (-not $SkipBuild) {
|
|
dotnet publish $projectPath -f net10.0-android -c Release -p:AndroidPackageFormat=apk -v minimal --no-restore
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "dotnet publish failed with exit code $LASTEXITCODE."
|
|
}
|
|
}
|
|
|
|
if (-not (Test-Path $releaseApkPath)) {
|
|
throw "Release APK was not found: $releaseApkPath"
|
|
}
|
|
|
|
$badging = & $aaptPath dump badging $releaseApkPath
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "aapt badging failed with exit code $LASTEXITCODE."
|
|
}
|
|
|
|
$badgingText = ($badging | Out-String)
|
|
$packageName = Get-ApkBadgingValue $badgingText "package: name='([^']+)'"
|
|
$versionCode = Get-ApkBadgingValue $badgingText "versionCode='([^']+)'"
|
|
$versionName = Get-ApkBadgingValue $badgingText "versionName='([^']+)'"
|
|
$targetSdk = Get-ApkBadgingValue $badgingText "targetSdkVersion:'([^']+)'"
|
|
$applicationLabel = Get-ApkBadgingValue $badgingText "application-label:'([^']+)'"
|
|
$nativeCodeLine = ($badging | Where-Object { $_ -like 'native-code:*' } | Select-Object -First 1)
|
|
$nativeCodes = if ($nativeCodeLine) {
|
|
[regex]::Matches($nativeCodeLine, "'([^']+)'") | ForEach-Object { $_.Groups[1].Value }
|
|
} else {
|
|
@()
|
|
}
|
|
|
|
if (-not $packageName) {
|
|
throw 'Package name could not be extracted from APK.'
|
|
}
|
|
|
|
if (-not $versionCode) {
|
|
throw 'Version code could not be extracted from APK.'
|
|
}
|
|
|
|
if (-not $versionName) {
|
|
throw 'Version name could not be extracted from APK.'
|
|
}
|
|
|
|
$signatureOutput = & $apksignerPath verify --print-certs $releaseApkPath
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "apksigner verification failed with exit code $LASTEXITCODE."
|
|
}
|
|
|
|
$signatureText = ($signatureOutput | Out-String)
|
|
$signerDn = Get-ApkBadgingValue $signatureText "Signer #1 certificate DN: (.+)"
|
|
$signerSha256 = Get-ApkBadgingValue $signatureText "Signer #1 certificate SHA-256 digest: ([A-Fa-f0-9]+)"
|
|
if ($signerDn) {
|
|
$signerDn = $signerDn.Trim()
|
|
}
|
|
if ($signerSha256) {
|
|
$signerSha256 = $signerSha256.Trim()
|
|
}
|
|
$isDebugSigned = $false
|
|
if ($signerDn -and $signerDn -like '*CN=Android Debug*') {
|
|
$isDebugSigned = $true
|
|
}
|
|
|
|
$resolvedName = if ($Name) { $Name } elseif ($applicationLabel) { $applicationLabel } else { 'Ikar' }
|
|
$resolvedVersion = if ($Version) { $Version } else { '{0}+{1}' -f $versionName, $versionCode }
|
|
$resolvedRepositoryUrl = if ($RepositoryUrl) { $RepositoryUrl } else { Get-RepositoryUrl }
|
|
$resolvedPlatform = Resolve-ArgusPlatform $nativeCodes
|
|
$apkItem = Get-Item $releaseApkPath
|
|
$apkHash = (Get-FileHash -Path $releaseApkPath -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
|
|
$stageDirectory = Join-Path $repoRoot ("artifacts\argus\{0}\v{1}" -f $Slug, $versionCode)
|
|
if (-not (Test-Path $stageDirectory)) {
|
|
New-Item -ItemType Directory -Path $stageDirectory -Force | Out-Null
|
|
}
|
|
|
|
$stagedApkName = "{0}-{1}.apk" -f $Slug, $versionCode
|
|
$stagedApkPath = Join-Path $stageDirectory $stagedApkName
|
|
Copy-Item $releaseApkPath $stagedApkPath -Force
|
|
|
|
$metadata = [ordered]@{
|
|
slug = $Slug
|
|
name = $resolvedName
|
|
summary = $Summary
|
|
description = $Description
|
|
version = $resolvedVersion
|
|
channel = $Channel
|
|
platform = $resolvedPlatform
|
|
packageKind = 'apk'
|
|
notes = if ($Notes) { $Notes } else { $null }
|
|
repositoryUrl = if ($resolvedRepositoryUrl) { $resolvedRepositoryUrl } else { $null }
|
|
homepageUrl = if ($HomepageUrl) { $HomepageUrl } else { $null }
|
|
androidPackageName = $packageName
|
|
androidVersionCode = [int]$versionCode
|
|
targetSdkVersion = if ($targetSdk) { [int]$targetSdk } else { $null }
|
|
isListed = (-not $Unlisted)
|
|
packagePath = $stagedApkPath
|
|
packageFileName = $stagedApkName
|
|
packageSizeBytes = $apkItem.Length
|
|
packageSha256 = $apkHash
|
|
preparedAt = [DateTimeOffset]::UtcNow.ToString('O')
|
|
verifiedFromApk = [ordered]@{
|
|
sourceApk = $releaseApkPath
|
|
applicationLabel = if ($applicationLabel) { $applicationLabel } else { $null }
|
|
packageName = $packageName
|
|
versionCode = [int]$versionCode
|
|
versionName = $versionName
|
|
targetSdkVersion = if ($targetSdk) { [int]$targetSdk } else { $null }
|
|
nativeCodes = $nativeCodes
|
|
signerDn = if ($signerDn) { $signerDn } else { $null }
|
|
signerSha256 = if ($signerSha256) { $signerSha256.ToLowerInvariant() } else { $null }
|
|
isDebugSigned = $isDebugSigned
|
|
}
|
|
}
|
|
|
|
$metadataPath = Join-Path $stageDirectory 'argus-package.json'
|
|
$metadata | ConvertTo-Json -Depth 6 | Set-Content -Path $metadataPath -Encoding UTF8
|
|
|
|
$summaryLines = @(
|
|
"slug=$Slug"
|
|
"name=$resolvedName"
|
|
"version=$resolvedVersion"
|
|
"channel=$Channel"
|
|
"platform=$resolvedPlatform"
|
|
"packageKind=apk"
|
|
"androidPackageName=$packageName"
|
|
"androidVersionCode=$versionCode"
|
|
"targetSdkVersion=$targetSdk"
|
|
"packagePath=$stagedApkPath"
|
|
"packageSizeBytes=$($apkItem.Length)"
|
|
"packageSha256=$apkHash"
|
|
"signerDn=$signerDn"
|
|
"signerSha256=$($signerSha256.ToLowerInvariant())"
|
|
"isDebugSigned=$isDebugSigned"
|
|
)
|
|
$summaryPath = Join-Path $stageDirectory 'argus-package-summary.txt'
|
|
$summaryLines | Set-Content -Path $summaryPath -Encoding UTF8
|
|
|
|
Write-Output "Prepared Argus package files."
|
|
Write-Output "APK: $stagedApkPath"
|
|
Write-Output "Metadata: $metadataPath"
|
|
Write-Output "Summary: $summaryPath"
|