NetDesk 1.5.0 — source release
This commit is contained in:
commit
40318aafdd
227 files changed
+80798
No files matched your search
@@ -0,0 +1,402 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
NetDesk - Android build pipeline.
|
||||
|
||||
Compiles the Rust FFI layer (netdesk-frb, a cdylib that statically links
|
||||
netdesk-core) into per-ABI .so libraries with cargo-ndk, drops them into
|
||||
the Flutter jniLibs tree, optionally provisions a release signing keystore,
|
||||
then produces the release APK, the per-ABI split APKs and the Play Store
|
||||
App Bundle (.aab).
|
||||
|
||||
Developed by Akronlogic (https://akronlogic.com).
|
||||
|
||||
.DESCRIPTION
|
||||
The Flutter UI (ui/) binds, via flutter_rust_bridge, to the compiled Rust
|
||||
library shipped as a shared object inside the APK/AAB under
|
||||
lib/<abi>/libnetdesk_frb.so (the Dart loader looks for stem 'netdesk_frb').
|
||||
This script is the single entry point that wires the Android toolchain:
|
||||
|
||||
netdesk-frb --cargo-ndk--> ui/android/app/src/main/jniLibs/<abi>/libnetdesk_frb.so
|
||||
Flutter --gradle-----> release .apk / split .apk / .aab (signed)
|
||||
|
||||
THE ONE COMMAND (from the repo root):
|
||||
|
||||
powershell -ExecutionPolicy Bypass -File packaging\build-android.ps1
|
||||
|
||||
First release-signed run (keystore does not exist yet):
|
||||
|
||||
$env:NETDESK_KEYSTORE_PASSWORD = '<your-strong-password>'
|
||||
powershell -ExecutionPolicy Bypass -File packaging\build-android.ps1 -GenerateKeystore
|
||||
|
||||
.NOTES
|
||||
Prerequisites (the script verifies / auto-provisions what it can):
|
||||
* Rust toolchain on PATH (cargo, rustup).
|
||||
* cargo-ndk (auto-installed with `cargo install cargo-ndk` if absent).
|
||||
* rustup Android std (auto-added if absent).
|
||||
* Android NDK (default C:\dev\android-ndk; override with -NdkHome
|
||||
or $env:ANDROID_NDK_HOME). REQUIRED for the Rust step.
|
||||
* Android SDK (default %LOCALAPPDATA%\Android\Sdk).
|
||||
* Flutter (default C:\dev\flutter\bin\flutter.bat).
|
||||
* A JDK for keytool / Gradle (Android Studio's JBR is auto-detected).
|
||||
|
||||
SECURITY: no real keystore password is ever stored in a committed file.
|
||||
key.properties and *.keystore are git-ignored. -GenerateKeystore reads the
|
||||
password ONLY from -KeystorePassword or $env:NETDESK_KEYSTORE_PASSWORD.
|
||||
#>
|
||||
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', 'KeystorePassword',
|
||||
Justification = 'keytool -storepass/-keypass require plaintext; sourced from an env var and never committed.')]
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
# Root of the Android NDK (contains source.properties + toolchains/llvm).
|
||||
[string] $NdkHome = $(if ($env:ANDROID_NDK_HOME) { $env:ANDROID_NDK_HOME } else { 'C:\dev\android-ndk' }),
|
||||
# Android SDK root.
|
||||
[string] $SdkHome = $(if ($env:ANDROID_HOME) { $env:ANDROID_HOME } elseif ($env:ANDROID_SDK_ROOT) { $env:ANDROID_SDK_ROOT } else { Join-Path $env:LOCALAPPDATA 'Android\Sdk' }),
|
||||
# JDK home for keytool + Gradle. Auto-detected (Android Studio JBR) when empty.
|
||||
[string] $JavaHome = $env:JAVA_HOME,
|
||||
# ABIs to build. arm64-v8a is the primary shipping ABI.
|
||||
[string[]] $Abis = @('arm64-v8a', 'armeabi-v7a', 'x86_64'),
|
||||
# Android API level the .so links against. Must be <= the Flutter minSdk.
|
||||
[int] $MinSdk = 21,
|
||||
# Cargo package compiled into the Android .so (the flutter_rust_bridge layer).
|
||||
[string] $RustPackage = 'netdesk-frb',
|
||||
# The crate's [lib] name -> the .so is lib<LibName>.so (what the Dart loader opens).
|
||||
[string] $LibName = 'netdesk_frb',
|
||||
# Which deliverables to produce.
|
||||
[ValidateSet('all', 'apk', 'split', 'aab', 'rust', 'none')]
|
||||
[string] $Target = 'all',
|
||||
# Skip the Rust/cargo-ndk step (reuse whatever .so are already in jniLibs).
|
||||
[switch] $SkipRust,
|
||||
# Create (if missing) the release keystore + key.properties before building.
|
||||
[switch] $GenerateKeystore,
|
||||
# Keystore / key password. NEVER hard-code this - pass it or set the env var.
|
||||
[string] $KeystorePassword = $env:NETDESK_KEYSTORE_PASSWORD,
|
||||
# Key alias inside the keystore.
|
||||
[string] $KeyAlias = 'netdesk',
|
||||
# Path to flutter.bat.
|
||||
[string] $FlutterBat = 'C:\dev\flutter\bin\flutter.bat'
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version 2.0
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Paths
|
||||
# --------------------------------------------------------------------------- #
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$RepoRoot = Split-Path -Parent $ScriptDir
|
||||
$UiDir = Join-Path $RepoRoot 'ui'
|
||||
$AndroidDir = Join-Path $UiDir 'android'
|
||||
$JniLibs = Join-Path $AndroidDir 'app\src\main\jniLibs'
|
||||
$CorePkg = $RustPackage
|
||||
$SoName = "lib$LibName.so"
|
||||
|
||||
# Flutter ABI name -> Rust target triple (for reporting / target checks).
|
||||
$AbiTriple = @{
|
||||
'arm64-v8a' = 'aarch64-linux-android'
|
||||
'armeabi-v7a' = 'armv7-linux-androideabi'
|
||||
'x86_64' = 'x86_64-linux-android'
|
||||
'x86' = 'i686-linux-android'
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Logging
|
||||
# --------------------------------------------------------------------------- #
|
||||
function Write-Step { param([string]$m) Write-Host "`n==> $m" -ForegroundColor Cyan }
|
||||
function Write-Info { param([string]$m) Write-Host " $m" -ForegroundColor Gray }
|
||||
function Write-Ok { param([string]$m) Write-Host " [ok] $m" -ForegroundColor Green }
|
||||
function Write-Warn2{ param([string]$m) Write-Host " [warn] $m" -ForegroundColor Yellow }
|
||||
function Die { param([string]$m) Write-Host "`n[FATAL] $m`n" -ForegroundColor Red; exit 1 }
|
||||
|
||||
function Invoke-Native {
|
||||
param([string]$Exe, [string[]]$ArgList, [string]$WorkDir)
|
||||
if ($WorkDir) { Push-Location $WorkDir }
|
||||
try {
|
||||
Write-Info "$Exe $($ArgList -join ' ')"
|
||||
& $Exe @ArgList
|
||||
if ($LASTEXITCODE -ne 0) { Die "'$Exe' exited with code $LASTEXITCODE" }
|
||||
} finally {
|
||||
if ($WorkDir) { Pop-Location }
|
||||
}
|
||||
}
|
||||
|
||||
$banner = @'
|
||||
_ _ _ ____ _ _ _ _ _
|
||||
| \ | | ___| |_| _ \ ___ ___| | _| | __ _ _ __ __| |_ __ ___ (_) __| |
|
||||
| \| |/ _ \ __| | | |/ _ \/ __| |/ / | / _` | '_ \ / _` | '__/ _ \| |/ _` |
|
||||
| |\ | __/ |_| |_| | __/\__ \ <| |___| (_| | | | | (_| | | | (_) | | (_| |
|
||||
|_| \_|\___|\__|____/ \___||___/_|\_\_____|\__,_|_| |_|\__,_|_| \___/|_|\__,_|
|
||||
'@
|
||||
Write-Host $banner -ForegroundColor Blue
|
||||
Write-Host " NetDesk Android build pipeline - Akronlogic" -ForegroundColor DarkGray
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Toolchain discovery / environment
|
||||
# --------------------------------------------------------------------------- #
|
||||
Write-Step 'Resolving toolchain'
|
||||
|
||||
if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) {
|
||||
Die 'cargo not found on PATH. Install Rust (https://rustup.rs) and re-open the shell.'
|
||||
}
|
||||
Write-Ok "cargo: $((cargo --version) 2>&1)"
|
||||
|
||||
# JAVA_HOME: auto-detect Android Studio's bundled JBR when not provided.
|
||||
if (-not $JavaHome -or -not (Test-Path (Join-Path $JavaHome 'bin'))) {
|
||||
$jbrCandidates = @(
|
||||
'C:\Program Files\Android\Android Studio\jbr',
|
||||
'C:\Program Files\Android\Android Studio\jre',
|
||||
(Join-Path ${env:ProgramFiles} 'Android\Android Studio\jbr')
|
||||
) | Where-Object { $_ -and (Test-Path (Join-Path $_ 'bin\java.exe')) }
|
||||
if ($jbrCandidates) { $JavaHome = $jbrCandidates[0] }
|
||||
}
|
||||
if ($JavaHome -and (Test-Path (Join-Path $JavaHome 'bin\java.exe'))) {
|
||||
$env:JAVA_HOME = $JavaHome
|
||||
$env:PATH = (Join-Path $JavaHome 'bin') + ';' + $env:PATH
|
||||
Write-Ok "JAVA_HOME: $JavaHome"
|
||||
} else {
|
||||
Write-Warn2 'No JDK found (JAVA_HOME unset and no Android Studio JBR). Gradle/keytool may fail.'
|
||||
}
|
||||
|
||||
# Android SDK.
|
||||
if (Test-Path $SdkHome) {
|
||||
$env:ANDROID_HOME = $SdkHome
|
||||
$env:ANDROID_SDK_ROOT = $SdkHome
|
||||
Write-Ok "ANDROID_HOME: $SdkHome"
|
||||
} else {
|
||||
Write-Warn2 "Android SDK not found at $SdkHome (Flutter build apk/aab needs it)."
|
||||
}
|
||||
|
||||
$needRust = (-not $SkipRust) -and ($Target -in @('all', 'apk', 'split', 'aab', 'rust'))
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Android NDK - detection (the script "detects it"; required only for Rust)
|
||||
# --------------------------------------------------------------------------- #
|
||||
function Test-Ndk {
|
||||
param([string]$Path)
|
||||
if (-not $Path) { return $false }
|
||||
if (-not (Test-Path (Join-Path $Path 'source.properties'))) { return $false }
|
||||
if (-not (Test-Path (Join-Path $Path 'toolchains\llvm\prebuilt'))) { return $false }
|
||||
return $true
|
||||
}
|
||||
|
||||
$ndkOk = Test-Ndk $NdkHome
|
||||
if ($needRust) {
|
||||
Write-Step 'Locating Android NDK'
|
||||
if ($ndkOk) {
|
||||
$rev = (Select-String -Path (Join-Path $NdkHome 'source.properties') -Pattern 'Pkg.Revision\s*=\s*(.+)' ).Matches.Groups[1].Value.Trim()
|
||||
$env:ANDROID_NDK_HOME = $NdkHome
|
||||
$env:ANDROID_NDK_ROOT = $NdkHome
|
||||
Write-Ok "NDK $rev at $NdkHome"
|
||||
} else {
|
||||
Die @"
|
||||
Android NDK not found (or incomplete) at:
|
||||
$NdkHome
|
||||
|
||||
Expected '$NdkHome\source.properties' and '$NdkHome\toolchains\llvm\prebuilt'.
|
||||
The NDK may still be downloading/extracting. When it is in place, re-run:
|
||||
|
||||
powershell -ExecutionPolicy Bypass -File packaging\build-android.ps1
|
||||
|
||||
Or point at another location: -NdkHome <path> / `$env:ANDROID_NDK_HOME
|
||||
Or skip the Rust step to build Flutter only: -SkipRust
|
||||
"@
|
||||
}
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Rust -> per-ABI .so via cargo-ndk
|
||||
# --------------------------------------------------------------------------- #
|
||||
if ($needRust) {
|
||||
Write-Step 'Preparing Rust Android targets'
|
||||
|
||||
# cargo-ndk (auto-install if missing).
|
||||
if (-not (Get-Command cargo-ndk -ErrorAction SilentlyContinue)) {
|
||||
Write-Warn2 'cargo-ndk not found - installing (cargo install cargo-ndk)...'
|
||||
Invoke-Native 'cargo' @('install', 'cargo-ndk')
|
||||
}
|
||||
Write-Ok "cargo-ndk: $((cargo ndk --version) 2>&1)"
|
||||
|
||||
# rustup std for each requested ABI (auto-add if missing).
|
||||
$installed = (rustup target list --installed) 2>&1
|
||||
foreach ($abi in $Abis) {
|
||||
$triple = $AbiTriple[$abi]
|
||||
if (-not $triple) { Die "Unknown ABI '$abi'. Valid: $($AbiTriple.Keys -join ', ')" }
|
||||
if ($installed -notcontains $triple) {
|
||||
Write-Warn2 "rustup target '$triple' missing - adding..."
|
||||
Invoke-Native 'rustup' @('target', 'add', $triple)
|
||||
} else {
|
||||
Write-Ok "rust target present: $triple"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Step "Building $CorePkg -> $SoName [$($Abis -join ', ')] (API $MinSdk)"
|
||||
New-Item -ItemType Directory -Force -Path $JniLibs | Out-Null
|
||||
|
||||
# cargo-ndk writes <JniLibs>/<abi>/<SoName> directly (that IS the "copy").
|
||||
# cargo ndk -t <abi>... -o <jniLibs> --platform <api> build --release --package <RustPackage> --lib
|
||||
$ndkArgs = @()
|
||||
foreach ($abi in $Abis) { $ndkArgs += @('-t', $abi) }
|
||||
$ndkArgs += '-o'; $ndkArgs += $JniLibs
|
||||
$ndkArgs += '--platform'; $ndkArgs += "$MinSdk"
|
||||
$ndkArgs += 'build'; $ndkArgs += '--release'
|
||||
$ndkArgs += '--package'; $ndkArgs += $CorePkg
|
||||
$ndkArgs += '--lib'
|
||||
|
||||
Invoke-Native 'cargo' (@('ndk') + $ndkArgs) $RepoRoot
|
||||
|
||||
Write-Step 'Verifying .so artifacts'
|
||||
$allOk = $true
|
||||
foreach ($abi in $Abis) {
|
||||
$so = Join-Path $JniLibs "$abi\$SoName"
|
||||
if (Test-Path $so) {
|
||||
$kb = [math]::Round((Get-Item $so).Length / 1KB)
|
||||
Write-Ok "$abi/$SoName ($kb KB)"
|
||||
} else {
|
||||
Write-Warn2 "MISSING: $abi/$SoName"
|
||||
$allOk = $false
|
||||
}
|
||||
}
|
||||
if (-not $allOk) { Die 'One or more ABIs failed to produce a .so (see cargo output above).' }
|
||||
|
||||
# ----------------------------------------------------------------------- #
|
||||
# Bundle the shared C++ STL beside the engine. The Rust cdylib links
|
||||
# against libc++_shared (pulled in by the openh264 C++ build), and Android
|
||||
# devices do NOT ship it — without this copy the app dies at startup with:
|
||||
# dlopen failed: library "libc++_shared.so" not found
|
||||
# (needed by .../lib/<abi>/libnetdesk_frb.so)
|
||||
# NOTE: the sysroot folder for armeabi-v7a is 'arm-linux-androideabi'
|
||||
# (NOT the Rust triple 'armv7-linux-androideabi').
|
||||
# ----------------------------------------------------------------------- #
|
||||
Write-Step 'Bundling libc++_shared.so per ABI'
|
||||
$SysrootLib = Join-Path $NdkHome 'toolchains\llvm\prebuilt\windows-x86_64\sysroot\usr\lib'
|
||||
$AbiSysDir = @{
|
||||
'arm64-v8a' = 'aarch64-linux-android'
|
||||
'armeabi-v7a' = 'arm-linux-androideabi'
|
||||
'x86_64' = 'x86_64-linux-android'
|
||||
}
|
||||
foreach ($abi in $Abis) {
|
||||
$src = Join-Path $SysrootLib "$($AbiSysDir[$abi])\libc++_shared.so"
|
||||
if (-not (Test-Path $src)) { Die "libc++_shared.so not found for ${abi}: $src" }
|
||||
Copy-Item $src (Join-Path $JniLibs "$abi\libc++_shared.so") -Force
|
||||
$kb = [math]::Round((Get-Item (Join-Path $JniLibs "$abi\libc++_shared.so")).Length / 1KB)
|
||||
Write-Ok "$abi/libc++_shared.so bundled ($kb KB)"
|
||||
}
|
||||
} else {
|
||||
Write-Step 'Skipping Rust step (-SkipRust or -Target none)'
|
||||
}
|
||||
|
||||
if ($Target -eq 'rust' -or $Target -eq 'none') {
|
||||
Write-Step 'Done (Rust-only target requested).'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Release signing (optional keystore provisioning)
|
||||
# --------------------------------------------------------------------------- #
|
||||
$KeyPropsFile = Join-Path $AndroidDir 'key.properties'
|
||||
$KeystoreFile = Join-Path $AndroidDir 'app\netdesk-release.keystore'
|
||||
|
||||
if ($GenerateKeystore) {
|
||||
Write-Step 'Provisioning release signing keystore'
|
||||
if (Test-Path $KeystoreFile) {
|
||||
Write-Ok "Keystore already exists: $KeystoreFile (not regenerating)"
|
||||
} else {
|
||||
if ([string]::IsNullOrWhiteSpace($KeystorePassword)) {
|
||||
Die @'
|
||||
-GenerateKeystore was requested but no password was supplied.
|
||||
Set it WITHOUT hard-coding it anywhere committed, e.g.:
|
||||
|
||||
$env:NETDESK_KEYSTORE_PASSWORD = '<your-strong-password>'
|
||||
... -GenerateKeystore
|
||||
|
||||
or pass -KeystorePassword '<your-strong-password>'.
|
||||
'@
|
||||
}
|
||||
$keytool = $null
|
||||
foreach ($c in @(
|
||||
(Join-Path $env:JAVA_HOME 'bin\keytool.exe'),
|
||||
'C:\Program Files\Android\Android Studio\jbr\bin\keytool.exe')) {
|
||||
if ($c -and (Test-Path $c)) { $keytool = $c; break }
|
||||
}
|
||||
if (-not $keytool) {
|
||||
$gc = Get-Command keytool -ErrorAction SilentlyContinue
|
||||
if ($gc) { $keytool = $gc.Source }
|
||||
}
|
||||
if (-not $keytool) { Die 'keytool not found (install a JDK or Android Studio).' }
|
||||
|
||||
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $KeystoreFile) | Out-Null
|
||||
Invoke-Native $keytool @(
|
||||
'-genkeypair', '-v',
|
||||
'-keystore', $KeystoreFile,
|
||||
'-alias', $KeyAlias,
|
||||
'-keyalg', 'RSA', '-keysize', '4096',
|
||||
'-validity', '10000',
|
||||
'-storepass', $KeystorePassword,
|
||||
'-keypass', $KeystorePassword,
|
||||
'-dname', 'CN=NetDesk, O=Akronlogic, OU=Release, C=DE'
|
||||
)
|
||||
Write-Ok "Created keystore: $KeystoreFile"
|
||||
}
|
||||
|
||||
# Write key.properties (git-ignored; holds the local password).
|
||||
@"
|
||||
# NetDesk release signing - LOCAL ONLY, never commit (git-ignored).
|
||||
# Regenerate the keystore with: build-android.ps1 -GenerateKeystore
|
||||
storePassword=$KeystorePassword
|
||||
keyPassword=$KeystorePassword
|
||||
keyAlias=$KeyAlias
|
||||
storeFile=$($KeystoreFile -replace '\\','/')
|
||||
"@ | Set-Content -Encoding ASCII -Path $KeyPropsFile
|
||||
Write-Ok "Wrote $KeyPropsFile"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $KeyPropsFile)) {
|
||||
Write-Warn2 'No key.properties found - release artifacts will be DEBUG-signed.'
|
||||
Write-Warn2 'Run with -GenerateKeystore (and a password) to produce upload-signed builds.'
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Flutter build
|
||||
# --------------------------------------------------------------------------- #
|
||||
if (-not (Test-Path $FlutterBat)) { Die "flutter.bat not found at $FlutterBat (use -FlutterBat)." }
|
||||
if (-not (Test-Path $AndroidDir)) {
|
||||
Die "Flutter android/ project missing at $AndroidDir. Run: flutter create --platforms=android . (in ui/)"
|
||||
}
|
||||
|
||||
Write-Step 'flutter pub get'
|
||||
Invoke-Native $FlutterBat @('pub', 'get') $UiDir
|
||||
|
||||
function Invoke-FlutterBuild {
|
||||
param([string[]]$ArgList, [string]$Label)
|
||||
Write-Step "flutter build $Label"
|
||||
Invoke-Native $FlutterBat (@('build') + $ArgList) $UiDir
|
||||
Write-Ok "$Label done"
|
||||
}
|
||||
|
||||
switch ($Target) {
|
||||
'apk' { Invoke-FlutterBuild @('apk', '--release') 'apk (fat)' }
|
||||
'split' { Invoke-FlutterBuild @('apk', '--release', '--split-per-abi') 'apk (split-per-abi)' }
|
||||
'aab' { Invoke-FlutterBuild @('appbundle', '--release') 'appbundle (.aab)' }
|
||||
'all' {
|
||||
Invoke-FlutterBuild @('apk', '--release') 'apk (fat)'
|
||||
Invoke-FlutterBuild @('apk', '--release', '--split-per-abi') 'apk (split-per-abi)'
|
||||
Invoke-FlutterBuild @('appbundle', '--release') 'appbundle (.aab)'
|
||||
}
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Report artifacts
|
||||
# --------------------------------------------------------------------------- #
|
||||
Write-Step 'Artifacts'
|
||||
$outApk = Join-Path $UiDir 'build\app\outputs\flutter-apk'
|
||||
$outAab = Join-Path $UiDir 'build\app\outputs\bundle\release'
|
||||
foreach ($dir in @($outApk, $outAab)) {
|
||||
if (Test-Path $dir) {
|
||||
Get-ChildItem $dir -Filter '*.ap*' -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
$mb = [math]::Round($_.Length / 1MB, 2)
|
||||
Write-Host (" {0,-34} {1,8} MB" -f $_.Name, $mb) -ForegroundColor Green
|
||||
Write-Host (" {0}" -f $_.FullName) -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-Host "`n==> NetDesk Android build complete.`n" -ForegroundColor Cyan
|
||||
Reference in new issue
Block a user