This commit is contained in:
2026-08-16 13:41:43 -03:00
parent 7b8e7bfbd0
commit 96ea761b72
76 changed files with 1399 additions and 3847 deletions
+337
View File
@@ -0,0 +1,337 @@
<#
.SYNOPSIS
Windows build for the DragonTCP Lite APK (no Gradle, no Android Studio).
.DESCRIPTION
Port of build_apk.sh. Compiles the Kotlin TUN adapter and the Java UI/service,
dexes them with d8, packages resources with aapt, aligns with zipalign and
signs with apksigner.
Run it from the directory that holds AndroidManifest.xml. The Go core lives
in <repo>/core; the repo root is located by walking up from this script until
a core\go.mod is found, so the script keeps working if the tree is nested.
Everything else is auto-detected where possible:
* Android SDK - ANDROID_SDK_ROOT / ANDROID_HOME, or -SdkRoot
* build-tools - newest installed version that has aapt/d8/apksigner/zipalign
* platform - android-35 if present, else newest installed
* JDK - JAVA_HOME, then javac on PATH, then C:\Program Files\Java\*
* Kotlin - KOTLIN_HOME, or downloaded once into .\.tools\kotlinc-<version>
* native lib - built via <repo>\build_core.ps1 if the .so is missing
.EXAMPLE
.\build_apk.ps1
.\build_apk.ps1 -BuildCore # rebuild the Go .so first
.\build_apk.ps1 -BuildTools 35.0.0 -Platform android-35
.\build_apk.ps1 -Keystore C:\keys\rel.jks -KsPass secret -KeyAlias rel -KeyPass secret
#>
[CmdletBinding()]
param(
[string]$SdkRoot,
[string]$BuildTools,
[string]$Platform,
[string]$JavaHome,
[string]$KotlinHome,
[string]$KotlinVersion = '2.1.21',
[string]$RepoRoot,
[string]$Keystore,
[string]$KsPass = 'dragontcp',
[string]$KeyAlias = 'dragontcp',
[string]$KeyPass = 'dragontcp',
# Rebuild the Go native library before packaging.
[switch]$BuildCore,
# Fail instead of downloading the Kotlin compiler.
[switch]$NoDownload
)
$ErrorActionPreference = 'Stop'
$Root = $PSScriptRoot
$ProgressPreference = 'SilentlyContinue' # Invoke-WebRequest is ~10x faster without it
function Step { param([string]$m) Write-Host "[apk] $m" -ForegroundColor Cyan }
function Die { param([string]$m) throw $m }
function Invoke-Tool {
param([string]$Exe, [string[]]$ToolArgs, [string]$What)
# These tools write harmless warnings to stderr (the JVM's sun.misc.Unsafe
# notice, apksigner's native-access notice). Under $ErrorActionPreference =
# 'Stop' with a merged stream those become terminating errors, so judge
# success by the exit code alone.
$saved = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try { & $Exe @ToolArgs } finally { $ErrorActionPreference = $saved }
if ($LASTEXITCODE -ne 0) { Die "$What failed (exit $LASTEXITCODE): $Exe" }
}
if (-not (Test-Path (Join-Path $Root 'AndroidManifest.xml'))) {
Die "No AndroidManifest.xml next to this script. Run it from the Android app directory."
}
# ------------------------------------------------------------------ repo root
if (-not $RepoRoot) {
# Walk up collecting every ancestor that looks like a checkout. A tree can
# contain more than one copy of core\, so prefer the one that also carries
# build_core.ps1 and only fall back to the nearest bare core\go.mod.
$fallback = $null
$probe = $Root
for ($i = 0; $i -lt 8 -and $probe; $i++) {
if (Test-Path (Join-Path $probe 'core\go.mod')) {
if (Test-Path (Join-Path $probe 'build_core.ps1')) { $RepoRoot = $probe; break }
if (-not $fallback) { $fallback = $probe }
}
$parent = Split-Path $probe -Parent
if ($parent -eq $probe) { break }
$probe = $parent
}
if (-not $RepoRoot) { $RepoRoot = $fallback }
}
if ($RepoRoot) { $RepoRoot = (Resolve-Path $RepoRoot).Path }
# ---------------------------------------------------------------- Android SDK
if (-not $SdkRoot) {
foreach ($c in $env:ANDROID_SDK_ROOT, $env:ANDROID_HOME,
"$env:LOCALAPPDATA\Android\Sdk", 'C:\Android\Sdk') {
if ($c -and (Test-Path $c)) { $SdkRoot = $c; break }
}
}
if (-not $SdkRoot -or -not (Test-Path $SdkRoot)) {
Die "Android SDK not found. Set ANDROID_SDK_ROOT (or pass -SdkRoot 'D:\Android\Sdk')."
}
$SdkRoot = (Resolve-Path $SdkRoot).Path
$needTools = @{ aapt = 'aapt.exe'; d8 = 'd8.bat'; apksigner = 'apksigner.bat'; zipalign = 'zipalign.exe' }
function Test-BuildTools {
param([string]$Dir)
foreach ($f in $needTools.Values) { if (-not (Test-Path (Join-Path $Dir $f))) { return $false } }
return $true
}
$btRoot = Join-Path $SdkRoot 'build-tools'
if (-not (Test-Path $btRoot)) { Die "No build-tools under $SdkRoot. Install them via the SDK Manager." }
if ($BuildTools) {
$BT = Join-Path $btRoot $BuildTools
if (-not (Test-BuildTools $BT)) { Die "build-tools $BuildTools is missing one of: $($needTools.Values -join ', ')" }
} else {
$candidates = Get-ChildItem $btRoot -Directory | Sort-Object {
try { [version]$_.Name } catch { [version]'0.0.0' }
} -Descending
$BT = $null
foreach ($c in $candidates) { if (Test-BuildTools $c.FullName) { $BT = $c.FullName; $BuildTools = $c.Name; break } }
if (-not $BT) { Die "No usable build-tools found under $btRoot (need $($needTools.Values -join ', '))." }
}
$platRoot = Join-Path $SdkRoot 'platforms'
if ($Platform) {
$AJ = Join-Path $platRoot "$Platform\android.jar"
} else {
$preferred = Join-Path $platRoot 'android-35\android.jar'
if (Test-Path $preferred) {
$Platform = 'android-35'; $AJ = $preferred
} else {
$p = Get-ChildItem $platRoot -Directory -ErrorAction SilentlyContinue |
Where-Object { Test-Path (Join-Path $_.FullName 'android.jar') } |
Sort-Object { [int]($_.Name -replace '\D', '') } -Descending |
Select-Object -First 1
if (-not $p) { Die "No android.jar found under $platRoot. Install a platform via the SDK Manager." }
$Platform = $p.Name; $AJ = Join-Path $p.FullName 'android.jar'
}
}
if (-not (Test-Path $AJ)) { Die "Android platform jar not found: $AJ" }
# ----------------------------------------------------------------------- JDK
if (-not $JavaHome) {
if ($env:JAVA_HOME -and (Test-Path "$env:JAVA_HOME\bin\javac.exe")) {
$JavaHome = $env:JAVA_HOME
} else {
$jc = Get-Command javac.exe -ErrorAction SilentlyContinue
# javapath\javac.exe is a shim: it can compile but has no jar/keytool next to it.
if ($jc -and (Test-Path (Join-Path (Split-Path $jc.Source) 'jar.exe'))) {
$JavaHome = Split-Path (Split-Path $jc.Source)
} else {
$j = Get-ChildItem 'C:\Program Files\Java', 'C:\Program Files\Eclipse Adoptium',
'C:\Program Files\Microsoft', 'C:\Program Files\Android\Android Studio' `
-Directory -ErrorAction SilentlyContinue |
Where-Object { Test-Path (Join-Path $_.FullName 'bin\javac.exe') } |
Sort-Object Name -Descending | Select-Object -First 1
if ($j) { $JavaHome = $j.FullName }
}
}
}
if (-not $JavaHome -or -not (Test-Path "$JavaHome\bin\javac.exe")) {
Die "No JDK found. Install a JDK (17 or 21 recommended) and set JAVA_HOME, or pass -JavaHome."
}
$JavaHome = (Resolve-Path $JavaHome).Path
$javaExe = "$JavaHome\bin\java.exe"
$javac = "$JavaHome\bin\javac.exe"
$jar = "$JavaHome\bin\jar.exe"
$keytool = "$JavaHome\bin\keytool.exe"
foreach ($t in $javaExe, $javac, $jar, $keytool) { if (-not (Test-Path $t)) { Die "Missing $t (a JRE is not enough - a full JDK is required)." } }
# apksigner.bat and d8.bat resolve java through JAVA_HOME.
$env:JAVA_HOME = $JavaHome
# -------------------------------------------------------------------- Kotlin
function Resolve-KotlinHome {
param([string]$Dir)
if (-not $Dir) { return $null }
if (Test-Path (Join-Path $Dir 'bin\kotlinc.bat')) { return (Resolve-Path $Dir).Path }
# Accept the parent of an extracted kotlin-compiler zip too.
$inner = Join-Path $Dir 'kotlinc'
if (Test-Path (Join-Path $inner 'bin\kotlinc.bat')) { return (Resolve-Path $inner).Path }
return $null
}
$toolsDir = Join-Path $Root '.tools'
if (-not $KotlinHome) {
foreach ($c in $env:KOTLIN_HOME, (Join-Path $toolsDir "kotlinc-$KotlinVersion")) {
$r = Resolve-KotlinHome $c
if ($r) { $KotlinHome = $r; break }
}
} else {
$KotlinHome = Resolve-KotlinHome $KotlinHome
if (-not $KotlinHome) { Die "No bin\kotlinc.bat under the -KotlinHome you passed." }
}
if (-not $KotlinHome) {
if ($NoDownload) { Die "Kotlin compiler not found. Set KOTLIN_HOME or drop -NoDownload." }
$dest = Join-Path $toolsDir "kotlinc-$KotlinVersion"
$zip = Join-Path $toolsDir "kotlin-compiler-$KotlinVersion.zip"
$url = "https://github.com/JetBrains/kotlin/releases/download/v$KotlinVersion/kotlin-compiler-$KotlinVersion.zip"
New-Item -ItemType Directory -Force -Path $toolsDir | Out-Null
Step "Downloading Kotlin $KotlinVersion (~85 MB, one time)..."
try {
Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing
} catch {
Die "Kotlin download failed: $($_.Exception.Message)`nDownload $url manually, extract it, and pass -KotlinHome <dir>\kotlinc."
}
Step 'Extracting Kotlin...'
if (Test-Path $dest) { Remove-Item $dest -Recurse -Force }
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory($zip, $dest)
Remove-Item $zip -Force
$KotlinHome = Resolve-KotlinHome $dest
if (-not $KotlinHome) { Die "Extracted Kotlin to $dest but found no bin\kotlinc.bat." }
}
# kotlinc.bat is deliberately NOT used: cmd.exe treats ';' as an argument
# delimiter, which shreds any -classpath we hand it. Drive the compiler jar directly.
$kotlinCompilerJar = Join-Path $KotlinHome 'lib\kotlin-compiler.jar'
if (-not (Test-Path $kotlinCompilerJar)) { Die "Missing $kotlinCompilerJar." }
$coro = Join-Path $KotlinHome 'lib\kotlinx-coroutines-core-jvm.jar'
$stdlib = @('kotlin-stdlib.jar', 'kotlin-stdlib-jdk7.jar', 'kotlin-stdlib-jdk8.jar') |
ForEach-Object { Join-Path $KotlinHome "lib\$_" } | Where-Object { Test-Path $_ }
if (-not (Test-Path $coro)) { Die "Missing $coro - use a Kotlin distribution that bundles kotlinx-coroutines-core-jvm.jar." }
if (-not $stdlib) { Die "No kotlin-stdlib jars under $KotlinHome\lib." }
# ----------------------------------------------------------------- native lib
$libDir = Join-Path $Root 'lib\arm64-v8a'
$so = Join-Path $libDir 'libdragontcp_client.so'
if ($BuildCore -or -not (Test-Path $so)) {
$why = if ($BuildCore) { '-BuildCore was requested' } else { 'libdragontcp_client.so is missing' }
if (-not $RepoRoot) { Die "$why but no core\go.mod was found above $Root. Pass -RepoRoot." }
$coreScript = Join-Path $RepoRoot 'build_core.ps1'
if (-not (Test-Path $coreScript)) { Die "$why but $coreScript was not found. Pass -RepoRoot." }
Step 'Native core (Go)...'
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $coreScript -ClientOnly -AndroidLibDir $libDir
if ($LASTEXITCODE -ne 0) { Die 'build_core.ps1 failed.' }
}
if (-not (Test-Path $so)) { Die "Missing $so - build the Go core first." }
Write-Host "app dir : $Root"
Write-Host "repo root : $(if ($RepoRoot) { $RepoRoot } else { '<not found>' })"
Write-Host "SDK : $SdkRoot"
Write-Host "build-tools: $BuildTools"
Write-Host "platform : $Platform"
Write-Host "JDK : $JavaHome"
Write-Host "Kotlin : $KotlinHome"
# -------------------------------------------------------------------- build
$B = Join-Path $Root 'build'
if (Test-Path $B) { Remove-Item $B -Recurse -Force }
New-Item -ItemType Directory -Force -Path "$B\kclasses", "$B\jclasses", "$B\dex" | Out-Null
Step 'Resources...'
Invoke-Tool (Join-Path $BT 'aapt.exe') @(
'package', '-f',
'-M', (Join-Path $Root 'AndroidManifest.xml'),
'-S', (Join-Path $Root 'res'),
'-A', (Join-Path $Root 'assets'),
'-I', $AJ,
'-F', "$B\resources.ap_"
) 'aapt'
Step 'Kotlin TUN adapter...'
$ktSources = Get-ChildItem "$Root\src" -Recurse -Filter *.kt | ForEach-Object { $_.FullName }
if (-not $ktSources) { Die "No .kt sources under $Root\src." }
$CP = (@($AJ, $coro) + $stdlib) -join ';'
Invoke-Tool $javaExe (@(
'-Xmx2g', '-Dfile.encoding=UTF-8', '-Djava.awt.headless=true',
'-cp', $kotlinCompilerJar, 'org.jetbrains.kotlin.cli.jvm.K2JVMCompiler',
'-nowarn', '-no-stdlib', '-jvm-target', '1.8',
'-kotlin-home', $KotlinHome,
'-classpath', $CP, '-d', "$B\kclasses"
) + $ktSources) 'kotlinc'
Step 'Java UI/service...'
$javaSources = Get-ChildItem "$Root\src" -Recurse -Filter *.java | ForEach-Object { $_.FullName }
if (-not $javaSources) { Die "No .java sources under $Root\src." }
$JCP = (@($AJ, $coro, "$B\kclasses") + $stdlib) -join ';'
# --release 8 keeps the API surface at Java 8; -bootclasspath is rejected by modern javac.
Invoke-Tool $javac (@('-nowarn', '--release', '8', '-classpath', $JCP, '-d', "$B\jclasses") + $javaSources) 'javac'
Invoke-Tool $jar @('cf', "$B\kclasses.jar", '-C', "$B\kclasses", '.') 'jar (kotlin)'
Invoke-Tool $jar @('cf', "$B\jclasses.jar", '-C', "$B\jclasses", '.') 'jar (java)'
Step 'DEX...'
Invoke-Tool (Join-Path $BT 'd8.bat') (@(
'--lib', $AJ, '--min-api', '29', '--output', "$B\dex",
"$B\kclasses.jar", "$B\jclasses.jar", $coro
) + $stdlib) 'd8'
Step 'Packaging...'
$unsigned = "$B\DragonTCP-Hybrid-unsigned.apk"
Copy-Item "$B\resources.ap_" $unsigned -Force
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zipFile = [System.IO.Compression.ZipFile]::Open($unsigned, 'Update')
try {
$level = [System.IO.Compression.CompressionLevel]::Optimal
foreach ($dex in Get-ChildItem "$B\dex" -Filter 'classes*.dex') {
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zipFile, $dex.FullName, $dex.Name, $level) | Out-Null
}
$libRoot = Join-Path $Root 'lib'
foreach ($f in Get-ChildItem $libRoot -Recurse -File) {
$entry = 'lib/' + $f.FullName.Substring($libRoot.Length + 1).Replace('\', '/')
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zipFile, $f.FullName, $entry, $level) | Out-Null
}
} finally {
$zipFile.Dispose()
}
Step 'Aligning...'
$aligned = "$B\DragonTCP-Hybrid-aligned.apk"
Invoke-Tool (Join-Path $BT 'zipalign.exe') @('-p', '-f', '4', $unsigned, $aligned) 'zipalign'
# ------------------------------------------------------------------- signing
if (-not $Keystore) { $Keystore = Join-Path $Root 'dragontcp-lite-debug.jks' }
if (-not (Test-Path $Keystore)) {
Step 'Generating debug keystore...'
Invoke-Tool $keytool @(
'-genkeypair', '-keystore', $Keystore, '-storepass', $KsPass, '-keypass', $KeyPass,
'-alias', $KeyAlias, '-keyalg', 'RSA', '-keysize', '2048', '-validity', '10000',
'-dname', 'CN=DragonTCP Lite,O=DragonTCP,C=US'
) 'keytool'
}
Step 'Signing...'
$out = "$B\DragonTCP-Hybrid-arm64.apk"
Invoke-Tool (Join-Path $BT 'apksigner.bat') @(
'sign', '--ks', $Keystore, '--ks-pass', "pass:$KsPass", '--key-pass', "pass:$KeyPass",
'--out', $out, $aligned
) 'apksigner sign'
Invoke-Tool (Join-Path $BT 'apksigner.bat') @('verify', '--verbose', $out) 'apksigner verify'
Write-Host ''
Write-Host "APK: $out" -ForegroundColor Green
Write-Host ("Size: {0:N1} MB" -f ((Get-Item $out).Length / 1MB))
Write-Host "Install with: adb install -r `"$out`""