- Package: de.krisenvorrat.* -> de.bollwerk.* - Klassen: KrisenvorratApp/Database/Theme -> Bollwerk* - ApplicationId: de.bollwerk.app - Server: BOLLWERK_* Env-Vars, bollwerk HOCON-Config - Docker: bollwerk-server/db/backup Container-Namen - Room DB: bollwerk.db, SharedPrefs: bollwerk_secure_prefs - Export-Dateien: bollwerk_export/inventar - UI-Strings, HTML, Admin-UI: alle auf Bollwerk - Docs, Skills, README angepasst - Alle Tests gruen, Build erfolgreich
378 lines
12 KiB
PowerShell
378 lines
12 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Zentrales Entwicklungsskript für die Bollwerk Android-App.
|
|
|
|
.DESCRIPTION
|
|
Handhabt Build, Deploy und Emulator-Operationen.
|
|
Behandelt bekannte Komplikationen (File-Locks, Boot-Delays, stderr-Warnungen).
|
|
|
|
.PARAMETER Action
|
|
Die auszuführende Aktion:
|
|
build - Debug-APK bauen
|
|
clean - Build-Verzeichnisse löschen
|
|
clean-build - Clean + Build in einem Schritt
|
|
emulator-start - Emulator starten und auf Boot warten
|
|
emulator-stop - Emulator beenden
|
|
install-emulator - APK auf Emulator installieren
|
|
install-device - APK auf physisches Gerät installieren
|
|
launch - App starten (auf dem aktiven Ziel)
|
|
hot-reload - Build + Install + Relaunch auf laufendem Ziel (ohne Emulator-Neustart)
|
|
deploy-emulator - Build + Install + Launch auf Emulator
|
|
deploy-device - Build + Install + Launch auf physisches Gerät
|
|
logcat - App-Logcat anzeigen (Ctrl+C zum Beenden)
|
|
devices - Verbundene Geräte auflisten
|
|
(Screenshot: separates Skript screenshot.ps1)
|
|
|
|
.PARAMETER Target
|
|
Zielgerät: 'emulator' (Standard) oder 'device'.
|
|
|
|
.EXAMPLE
|
|
& ".github/skills/android-build/android-dev.ps1" -Action build
|
|
& ".github/skills/android-build/android-dev.ps1" -Action deploy-emulator
|
|
& ".github/skills/android-build/android-dev.ps1" -Action logcat -Target device
|
|
#>
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[ValidateSet(
|
|
'build', 'clean', 'clean-build',
|
|
'emulator-start', 'emulator-stop',
|
|
'install-emulator', 'install-device',
|
|
'launch', 'hot-reload',
|
|
'deploy-emulator', 'deploy-device',
|
|
'logcat', 'devices'
|
|
)]
|
|
[string]$Action,
|
|
|
|
[ValidateSet('emulator', 'device')]
|
|
[string]$Target = 'emulator'
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
# --- Konfiguration ---
|
|
$SDK_ROOT = "C:\Users\JensR\AppData\Local\Android\Sdk"
|
|
$ADB = "$SDK_ROOT\platform-tools\adb.exe"
|
|
$EMULATOR = "$SDK_ROOT\emulator\emulator.exe"
|
|
$PROJECT_DIR = $PSScriptRoot | Split-Path | Split-Path | Split-Path # .github/skills/android-build → repo root
|
|
$APK_PATH = "$PROJECT_DIR\app\build\outputs\apk\debug\app-debug.apk"
|
|
$AVD_NAME = "S24Ultra_API35"
|
|
$GPU_MODE = "guest" # 'guest' = Software-Rendering im Gast (host/auto/swiftshader scheitern an fehlendem OpenGL Core Profile)
|
|
$PACKAGE = "de.bollwerk.app"
|
|
$ACTIVITY = "$PACKAGE/.MainActivity"
|
|
$BOOT_TIMEOUT = 300 # Sekunden (erster Boot eines neuen AVD kann >2min dauern)
|
|
$ADB_CONNECT_TIMEOUT = 120 # Sekunden auf ADB-Verbindung warten
|
|
$PM_WAIT = 30 # Sekunden nach Boot auf PackageManager warten
|
|
$INSTALL_RETRY = 3
|
|
|
|
$env:ANDROID_HOME = $SDK_ROOT
|
|
|
|
# --- Hilfsfunktionen ---
|
|
function Write-Step { param([string]$msg) Write-Host ">> $msg" -ForegroundColor Cyan }
|
|
function Write-Ok { param([string]$msg) Write-Host "OK $msg" -ForegroundColor Green }
|
|
function Write-Err { param([string]$msg) Write-Host "ERR $msg" -ForegroundColor Red }
|
|
function Write-Warn { param([string]$msg) Write-Host "WARN $msg" -ForegroundColor Yellow }
|
|
|
|
function Get-AdbTarget {
|
|
if ($Target -eq 'device') { return '-d' }
|
|
return '-e'
|
|
}
|
|
|
|
function Test-DeviceConnected {
|
|
param([string]$type)
|
|
$flag = if ($type -eq 'device') { '-d' } else { '-e' }
|
|
try {
|
|
$result = & $ADB $flag get-state 2>&1 | Out-String
|
|
return ($result.Trim() -eq 'device')
|
|
}
|
|
catch {
|
|
return $false
|
|
}
|
|
}
|
|
|
|
function Invoke-Gradle {
|
|
param([string[]]$Tasks)
|
|
|
|
Write-Step "Gradle: $($Tasks -join ' ')"
|
|
Push-Location $PROJECT_DIR
|
|
try {
|
|
$output = & .\gradlew.bat @Tasks 2>&1 | Out-String
|
|
$success = $output -match 'BUILD SUCCESSFUL'
|
|
$failed = $output -match 'BUILD FAILED'
|
|
|
|
if ($success) {
|
|
$duration = if ($output -match 'in (\d+[ms]\s?\d*\w*)') { $Matches[0] } else { '' }
|
|
Write-Ok "BUILD SUCCESSFUL $duration"
|
|
return $true
|
|
}
|
|
elseif ($failed) {
|
|
# Fehlerdetails extrahieren
|
|
$errorBlock = ($output -split "`n" | Select-String -Pattern "What went wrong|ERROR:|FAILURE:" -Context 0, 5) -join "`n"
|
|
Write-Err "BUILD FAILED"
|
|
Write-Host $errorBlock -ForegroundColor Red
|
|
return $false
|
|
}
|
|
else {
|
|
Write-Warn "Build-Status unklar. Output prüfen:"
|
|
Write-Host ($output | Select-Object -Last 20)
|
|
return $false
|
|
}
|
|
}
|
|
finally {
|
|
Pop-Location
|
|
}
|
|
}
|
|
|
|
function Remove-BuildDirs {
|
|
Write-Step "Build-Verzeichnisse löschen"
|
|
$dirs = @("$PROJECT_DIR\app\build", "$PROJECT_DIR\build")
|
|
foreach ($d in $dirs) {
|
|
if (Test-Path $d) {
|
|
Remove-Item $d -Recurse -Force -ErrorAction SilentlyContinue
|
|
if (Test-Path $d) {
|
|
Write-Warn "Konnte $d nicht vollständig löschen (File-Lock?). Versuche erneut..."
|
|
Start-Sleep -Seconds 2
|
|
Remove-Item $d -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
}
|
|
Write-Ok "Build-Verzeichnisse bereinigt"
|
|
}
|
|
|
|
function Start-Emulator {
|
|
# Prüfe ob Emulator bereits läuft
|
|
if (Test-DeviceConnected 'emulator') {
|
|
Write-Ok "Emulator läuft bereits"
|
|
return $true
|
|
}
|
|
|
|
Write-Step "Emulator starten: $AVD_NAME (GPU: $GPU_MODE)"
|
|
Start-Process -FilePath $EMULATOR -ArgumentList "-avd $AVD_NAME -gpu $GPU_MODE -no-snapshot-load" -WindowStyle Normal
|
|
|
|
Write-Step "Warte auf ADB-Verbindung (max ${ADB_CONNECT_TIMEOUT}s)..."
|
|
$connectElapsed = 0
|
|
do {
|
|
Start-Sleep -Seconds 5
|
|
$connectElapsed += 5
|
|
$connected = Test-DeviceConnected 'emulator'
|
|
if ($connectElapsed % 15 -eq 0) { Write-Host " ... ${connectElapsed}s" -ForegroundColor DarkGray }
|
|
} while (-not $connected -and $connectElapsed -lt $ADB_CONNECT_TIMEOUT)
|
|
|
|
if (-not $connected) {
|
|
Write-Err "Emulator nicht erreichbar nach ${ADB_CONNECT_TIMEOUT}s. Prüfe GPU-Treiber und Hypervisor."
|
|
return $false
|
|
}
|
|
Write-Ok "ADB verbunden nach ${connectElapsed}s"
|
|
|
|
Write-Step "Warte auf Boot (max ${BOOT_TIMEOUT}s)..."
|
|
$elapsed = 0
|
|
do {
|
|
Start-Sleep -Seconds 5
|
|
$elapsed += 5
|
|
$boot = (& $ADB -e shell getprop sys.boot_completed 2>$null | Out-String).Trim()
|
|
if ($elapsed % 15 -eq 0) { Write-Host " ... ${elapsed}s" -ForegroundColor DarkGray }
|
|
} while ($boot -ne "1" -and $elapsed -lt $BOOT_TIMEOUT)
|
|
|
|
if ($boot -ne "1") {
|
|
Write-Err "Emulator-Boot Timeout nach ${BOOT_TIMEOUT}s"
|
|
return $false
|
|
}
|
|
|
|
# Extra-Pause für PackageManager-Initialisierung
|
|
# PackageManager braucht deutlich länger als sys.boot_completed anzeigt
|
|
Write-Step "Warte ${PM_WAIT}s auf PackageManager..."
|
|
Start-Sleep -Seconds $PM_WAIT
|
|
|
|
Write-Ok "Emulator gebootet nach ${elapsed}s + ${PM_WAIT}s PM-Wait"
|
|
return $true
|
|
}
|
|
|
|
function Install-Apk {
|
|
param([string]$targetType)
|
|
|
|
if (-not (Test-Path $APK_PATH)) {
|
|
Write-Err "APK nicht gefunden: $APK_PATH"
|
|
Write-Err "Bitte zuerst bauen: -Action build"
|
|
return $false
|
|
}
|
|
|
|
$flags = Get-AdbTarget
|
|
$apkSize = [math]::Round((Get-Item $APK_PATH).Length / 1MB, 1)
|
|
Write-Step "APK installieren ${apkSize} MB auf $targetType"
|
|
|
|
for ($attempt = 1; $attempt -le $INSTALL_RETRY; $attempt++) {
|
|
$result = & $ADB $flags install -r $APK_PATH 2>&1 | Out-String
|
|
if ($result -match 'Success') {
|
|
Write-Ok "APK installiert"
|
|
return $true
|
|
}
|
|
if ($attempt -lt $INSTALL_RETRY) {
|
|
Write-Warn "Install fehlgeschlagen (Versuch $attempt/$INSTALL_RETRY). Warte 5s..."
|
|
Start-Sleep -Seconds 5
|
|
}
|
|
}
|
|
|
|
Write-Err "APK-Installation fehlgeschlagen nach $INSTALL_RETRY Versuchen:"
|
|
Write-Host $result -ForegroundColor Red
|
|
return $false
|
|
}
|
|
|
|
function Start-App {
|
|
$flags = Get-AdbTarget
|
|
Write-Step "App starten: $ACTIVITY"
|
|
$result = & $ADB $flags shell am start -n $ACTIVITY 2>&1 | Out-String
|
|
if ($result -match 'Error|Exception') {
|
|
Write-Err "App-Start fehlgeschlagen:"
|
|
Write-Host $result -ForegroundColor Red
|
|
return $false
|
|
}
|
|
Write-Ok "App gestartet"
|
|
return $true
|
|
}
|
|
|
|
# --- Aktionen ---
|
|
switch ($Action) {
|
|
|
|
'build' {
|
|
$ok = Invoke-Gradle @('assembleDebug')
|
|
if ($ok -and (Test-Path $APK_PATH)) {
|
|
$size = [math]::Round((Get-Item $APK_PATH).Length / 1MB, 2)
|
|
Write-Ok "APK: $APK_PATH - ${size} MB"
|
|
}
|
|
exit ([int](-not $ok))
|
|
}
|
|
|
|
'clean' {
|
|
Remove-BuildDirs
|
|
exit 0
|
|
}
|
|
|
|
'clean-build' {
|
|
Remove-BuildDirs
|
|
$ok = Invoke-Gradle @('assembleDebug')
|
|
if ($ok -and (Test-Path $APK_PATH)) {
|
|
$size = [math]::Round((Get-Item $APK_PATH).Length / 1MB, 2)
|
|
Write-Ok "APK: $APK_PATH - ${size} MB"
|
|
}
|
|
exit ([int](-not $ok))
|
|
}
|
|
|
|
'emulator-start' {
|
|
$ok = Start-Emulator
|
|
exit ([int](-not $ok))
|
|
}
|
|
|
|
'emulator-stop' {
|
|
Write-Step "Emulator beenden"
|
|
& $ADB emu kill 2>$null
|
|
Start-Sleep -Seconds 3
|
|
# Fallback: Prozesse killen falls emu kill nicht wirkt
|
|
$remaining = Get-Process -Name "qemu-system*", "emulator" -ErrorAction SilentlyContinue
|
|
if ($remaining) {
|
|
Write-Warn "Emulator-Prozess reagiert nicht auf emu kill. Force-Kill..."
|
|
$remaining | Stop-Process -Force -ErrorAction SilentlyContinue
|
|
Start-Sleep -Seconds 2
|
|
}
|
|
Stop-Process -Name "crashpad*" -Force -ErrorAction SilentlyContinue
|
|
Write-Ok "Emulator gestoppt"
|
|
exit 0
|
|
}
|
|
|
|
'install-emulator' {
|
|
$Target = 'emulator'
|
|
if (-not (Test-DeviceConnected 'emulator')) {
|
|
Write-Err "Kein Emulator verbunden. Starte mit: -Action emulator-start"
|
|
exit 1
|
|
}
|
|
$ok = Install-Apk 'emulator'
|
|
exit ([int](-not $ok))
|
|
}
|
|
|
|
'install-device' {
|
|
$Target = 'device'
|
|
if (-not (Test-DeviceConnected 'device')) {
|
|
Write-Err "Kein physisches Gerät verbunden. USB-Debugging prüfen."
|
|
exit 1
|
|
}
|
|
$ok = Install-Apk 'device'
|
|
exit ([int](-not $ok))
|
|
}
|
|
|
|
'launch' {
|
|
$ok = Start-App
|
|
exit ([int](-not $ok))
|
|
}
|
|
|
|
'hot-reload' {
|
|
# Build + Install + Relaunch auf bereits laufendem Emulator/Gerät.
|
|
# Kein Emulator-Start, kein Snapshot-Laden — spart 60-90s.
|
|
$targetType = $Target
|
|
if (-not (Test-DeviceConnected $targetType)) {
|
|
Write-Err "Kein $targetType verbunden. Starte zuerst mit: -Action emulator-start"
|
|
exit 1
|
|
}
|
|
|
|
$ok = Invoke-Gradle @('assembleDebug')
|
|
if (-not $ok) { exit 1 }
|
|
|
|
# App stoppen bevor Install (verhindert 'Activity not started' Race Condition)
|
|
Write-Step "App stoppen"
|
|
$flags = Get-AdbTarget
|
|
& $ADB $flags shell am force-stop $PACKAGE 2>$null
|
|
|
|
$ok = Install-Apk $targetType
|
|
if (-not $ok) { exit 1 }
|
|
|
|
$ok = Start-App
|
|
exit ([int](-not $ok))
|
|
}
|
|
|
|
'deploy-emulator' {
|
|
$Target = 'emulator'
|
|
$ok = Start-Emulator
|
|
if (-not $ok) { exit 1 }
|
|
|
|
$ok = Invoke-Gradle @('assembleDebug')
|
|
if (-not $ok) { exit 1 }
|
|
|
|
$ok = Install-Apk 'emulator'
|
|
if (-not $ok) { exit 1 }
|
|
|
|
$ok = Start-App
|
|
exit ([int](-not $ok))
|
|
}
|
|
|
|
'deploy-device' {
|
|
$Target = 'device'
|
|
if (-not (Test-DeviceConnected 'device')) {
|
|
Write-Err "Kein physisches Gerät verbunden."
|
|
exit 1
|
|
}
|
|
|
|
$ok = Invoke-Gradle @('assembleDebug')
|
|
if (-not $ok) { exit 1 }
|
|
|
|
$ok = Install-Apk 'device'
|
|
if (-not $ok) { exit 1 }
|
|
|
|
$ok = Start-App
|
|
exit ([int](-not $ok))
|
|
}
|
|
|
|
'logcat' {
|
|
$flags = Get-AdbTarget
|
|
Write-Step "Logcat für $PACKAGE (Ctrl+C zum Beenden)"
|
|
$pid = & $ADB $flags shell pidof $PACKAGE 2>$null
|
|
if ($pid) {
|
|
& $ADB $flags logcat --pid=$pid -v time
|
|
}
|
|
else {
|
|
Write-Warn "App läuft nicht. Zeige alle Logs mit Tag-Filter..."
|
|
& $ADB $flags logcat -v time *:W
|
|
}
|
|
}
|
|
|
|
'devices' {
|
|
Write-Step "Verbundene Geräte"
|
|
& $ADB devices -l
|
|
}
|
|
}
|