<# Build helper for Android (Windows PowerShell) What it does: - Checks for required commands (`npm`, `rustup`, `cargo`, `cargo-ndk`) - Builds frontend (runs `npm run build` if `dist`/`build` not present) - Copies frontend files from `dist` or `src` into `android/app/src/main/assets` - Builds Rust native libs using `cargo-ndk` (if available) for `aarch64` and `armv7` - Copies produced `.so` files into `android/app/src/main/jniLibs/*` Note: This script prepares the Android project. To produce the APK, open `android/` in Android Studio and run Build -> Assemble, or run `gradlew assembleDebug` locally. #> Set-StrictMode -Version Latest function Check-Command($name) { $which = Get-Command $name -ErrorAction SilentlyContinue return $which -ne $null } Write-Output "Starting Android prep script..." if (-not (Check-Command npm)) { Write-Warning "npm not found in PATH. Install Node.js to build frontend." } if (-not (Check-Command rustup)) { Write-Warning "rustup not found in PATH. Install Rust toolchain." } if (-not (Check-Command cargo)) { Write-Warning "cargo not found in PATH." } $cargoNdkAvailable = Check-Command cargo-ndk if (-not $cargoNdkAvailable) { Write-Warning "cargo-ndk not found. Native libs will not be built. Install via 'cargo install cargo-ndk'" } # Determine repository root (parent of the scripts folder) $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition $root = Split-Path -Parent $scriptDir Push-Location $root # Prefer Tauri-generated Android Studio project (tauri android init) $androidRoot = Join-Path $root 'src-tauri\gen\android' if (-not (Test-Path $androidRoot)) { # Legacy fallback (non-Tauri project) $androidRoot = Join-Path $root 'android' } function Escape-LocalPropertiesPath([string]$p) { # local.properties expects ':' escaped and backslashes doubled on Windows. # Use plain string replacements to avoid regex escaping pitfalls. return ($p.Replace('\', '\\').Replace(':', '\:')) } # Ensure Android SDK/NDK locations are set for Gradle (local.properties) $sdkRoot = $env:ANDROID_SDK_ROOT if (-not $sdkRoot) { $sdkRoot = $env:ANDROID_HOME } if (-not $sdkRoot) { $sdkRoot = Join-Path $env:LOCALAPPDATA 'Android\Sdk' } $ndkRoot = $env:ANDROID_NDK_ROOT if (-not $ndkRoot) { $ndkRoot = $env:ANDROID_NDK_HOME } if (-not $ndkRoot -and (Test-Path (Join-Path $sdkRoot 'ndk'))) { $ndkVersions = Get-ChildItem -Path (Join-Path $sdkRoot 'ndk') -Directory -ErrorAction SilentlyContinue | Sort-Object Name -Descending if ($ndkVersions -and (@($ndkVersions)).Count -gt 0) { $ndkRoot = @($ndkVersions)[0].FullName } } if (Test-Path $androidRoot) { $localPropsPath = Join-Path $androidRoot 'local.properties' $lines = @() if ($sdkRoot) { $lines += "sdk.dir=$(Escape-LocalPropertiesPath $sdkRoot)" } if ($ndkRoot) { $lines += "ndk.dir=$(Escape-LocalPropertiesPath $ndkRoot)" } if ($lines.Count -gt 0) { Set-Content -Path $localPropsPath -Value ($lines -join "`n") -Encoding ASCII Write-Output "Wrote Android SDK/NDK config to: $localPropsPath" } } # Build frontend (optional) Write-Output "Preparing frontend files..." $distDirs = @('dist','build') $foundDist = $null foreach ($d in $distDirs) { if (Test-Path (Join-Path $root $d)) { $foundDist = $d; break } } if (-not $foundDist) { # IMPORTANT: `npm run build` in this repo runs `tauri build`, which is a desktop bundling step. # For Android prep we only need web assets, so we fall back to copying `src/` as assets. Write-Warning "No dist/build output found — copying `src/` as assets (skipping `npm run build` to avoid desktop bundling)." } $assetsDst = Join-Path $androidRoot 'app\src\main\assets' if (-not (Test-Path $assetsDst)) { New-Item -ItemType Directory -Path $assetsDst -Force | Out-Null } if ($foundDist) { Write-Output "Copying frontend from '$foundDist' to Android assets..." robocopy (Join-Path $root $foundDist) $assetsDst /MIR | Out-Null } else { Write-Output "Copying raw 'src' to Android assets..." robocopy (Join-Path $root 'src') $assetsDst /MIR | Out-Null } # Build native libs if cargo-ndk available if ($cargoNdkAvailable) { Write-Output "Building Rust native libs via cargo-ndk from project root: $root" try { # Build from the Rust crate directory `src-tauri` $crateDir = Join-Path $root 'src-tauri' if (-not (Test-Path (Join-Path $crateDir 'Cargo.toml'))) { Write-Warning "Cargo.toml not found in src-tauri; skipping native build." } else { # Prefer Ninja generator for CMake if available (avoids Visual Studio generator issues) # Restore env vars at the end so we don't pollute the current PowerShell session. $oldCmakeGenerator = $env:CMAKE_GENERATOR $oldCmakeMakeProgram = $env:CMAKE_MAKE_PROGRAM $ninjaCmd = Get-Command ninja -ErrorAction SilentlyContinue if ($ninjaCmd) { Write-Output "Ninja detected at $($ninjaCmd.Source); setting CMake generator to Ninja." $env:CMAKE_GENERATOR = 'Ninja' $env:CMAKE_MAKE_PROGRAM = $ninjaCmd.Source } else { Write-Warning "Ninja not found in PATH. Installing Ninja or adding it to PATH is strongly recommended to avoid Visual Studio CMake generator on Windows." } # Attempt to locate Android NDK if environment variables are not set if (-not $env:ANDROID_NDK_ROOT -and -not $env:ANDROID_NDK_HOME) { $candidates = @() if ($env:ANDROID_SDK_ROOT) { $candidates += Join-Path $env:ANDROID_SDK_ROOT 'ndk' } if ($env:ANDROID_HOME) { $candidates += Join-Path $env:ANDROID_HOME 'ndk' } $candidates += Join-Path $env:LOCALAPPDATA 'Android\sdk\ndk' $candidates += Join-Path $env:USERPROFILE 'AppData\Local\Android\sdk\ndk' $candidates += 'C:\Program Files (x86)\Android\AndroidNDK' foreach ($cand in $candidates) { if (Test-Path $cand) { $versions = Get-ChildItem -Path $cand -Directory -ErrorAction SilentlyContinue | Sort-Object Name -Descending if ($versions -and (@($versions)).Count -gt 0) { $ndkPath = @($versions)[0].FullName Write-Output "Detected Android NDK at: $ndkPath" $env:ANDROID_NDK_ROOT = $ndkPath $env:ANDROID_NDK = $ndkPath break } } } if (-not $env:ANDROID_NDK_ROOT) { Write-Warning "ANDROID_NDK_ROOT/ANDROID_NDK not set and no NDK found in common locations. Set ANDROID_NDK_ROOT to your NDK path." } } else { Write-Output "Using existing ANDROID_NDK_ROOT: $($env:ANDROID_NDK_ROOT)" if (-not $env:ANDROID_NDK) { $env:ANDROID_NDK = $env:ANDROID_NDK_ROOT } } # Ensure expected external binary placeholders exist so Tauri bundling doesn't fail $binariesDir = Join-Path $crateDir 'binaries' if (-not (Test-Path $binariesDir)) { New-Item -ItemType Directory -Path $binariesDir -Force | Out-Null } $placeholder1 = Join-Path $binariesDir 'RadioPlayer-aarch64-linux-android' $placeholder2 = Join-Path $binariesDir 'RadioPlayer-armv7-linux-androideabi' if (-not (Test-Path $placeholder1)) { New-Item -ItemType File -Path $placeholder1 -Force | Out-Null; Write-Output "Created placeholder: $placeholder1" } if (-not (Test-Path $placeholder2)) { New-Item -ItemType File -Path $placeholder2 -Force | Out-Null; Write-Output "Created placeholder: $placeholder2" } # If a previous build used a different CMake generator (e.g., Visual Studio), aws-lc-sys can fail with # "Does not match the generator used previously". Clean only the aws-lc-sys CMake build dirs. $awsLcBuildDirs = Get-ChildItem -Path (Join-Path $crateDir 'target') -Recurse -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -like 'aws-lc-sys-*' } foreach ($d in @($awsLcBuildDirs)) { $cmakeBuildDir = Join-Path $d.FullName 'out\build' $cmakeCache = Join-Path $cmakeBuildDir 'CMakeCache.txt' if (Test-Path $cmakeCache) { Write-Output "Cleaning stale CMake cache for aws-lc-sys: $cmakeBuildDir" Remove-Item -Path $cmakeBuildDir -Recurse -Force -ErrorAction SilentlyContinue } } Push-Location $crateDir try { # Use API 24 to ensure libc symbols like getifaddrs/freeifaddrs are available. # Build only the library to avoid linking the desktop binary for Android. Write-Output "Running: cargo ndk -t arm64-v8a -t armeabi-v7a -P 24 build --release --lib (in $crateDir)" cargo ndk -t arm64-v8a -t armeabi-v7a -P 24 build --release --lib } finally { Pop-Location if ($null -eq $oldCmakeGenerator) { Remove-Item Env:\CMAKE_GENERATOR -ErrorAction SilentlyContinue } else { $env:CMAKE_GENERATOR = $oldCmakeGenerator } if ($null -eq $oldCmakeMakeProgram) { Remove-Item Env:\CMAKE_MAKE_PROGRAM -ErrorAction SilentlyContinue } else { $env:CMAKE_MAKE_PROGRAM = $oldCmakeMakeProgram } } # Search for produced .so files under src-tauri/target $soFiles = Get-ChildItem -Path (Join-Path $crateDir 'target') -Recurse -Filter "*.so" -ErrorAction SilentlyContinue if (-not $soFiles) { Write-Warning "No .so files found after build. Check cargo-ndk output above for errors." } else { foreach ($f in @($soFiles)) { $full = $f.FullName if ($full -match 'aarch64|aarch64-linux-android|arm64-v8a') { $abi = 'arm64-v8a' } elseif ($full -match 'armv7|armv7-linux-androideabi|armeabi-v7a') { $abi = 'armeabi-v7a' } else { continue } $dst = Join-Path $androidRoot "app\src\main\jniLibs\$abi" if (-not (Test-Path $dst)) { New-Item -ItemType Directory -Path $dst -Force | Out-Null } Copy-Item $full -Destination $dst -Force Write-Output "Copied $($f.Name) -> $dst" } } } } catch { Write-Warning "cargo-ndk build failed. Exception: $($_.Exception.Message)" if ($_.ScriptStackTrace) { Write-Output $_.ScriptStackTrace } } } else { Write-Warning "Skipping native lib build (cargo-ndk missing)." } Write-Output "Android prep complete. Open '$androidRoot' in Android Studio and build the APK (or run './gradlew assembleDebug' in that folder)." Pop-Location