NetDesk 1.5.0 — source release

This commit is contained in:
NetDesk committed 2026-10-18 02:02:46 +00:00
commit 40318aafdd
227 files changed
+80798

No files matched your search

+402
View File
@@ -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