From a993c24f798fd6826ce3763322fd3653f0166d5e Mon Sep 17 00:00:00 2001 From: neon798 <209852807+neon798@users.noreply.github.com> Date: Fri, 10 Jul 2026 07:45:38 -0600 Subject: [PATCH 1/4] feat(android): durable Android build pipeline + GitHub Actions compile Local full-source Android builds OOM the dev box (rustc peaks ~17GB RSS; kernel oom-killer hung the machine 2026-07-10 02:56), so compilation moves to a hosted runner with a 16GB swapfile absorbing the rustc peak. - patches/android/fenix-neonwolf-appid.patch: capture the manual tree edits (Fenix applicationId org.neonwolf + .browser/.browser.debug suffixes, app_name -> Neonwolf), wired into assets/patches.txt so `make dir` reproduces the tree from scratch - make build-android: normalize browser/config/version.txt to the pure Firefox version (Android Gradle computeVersionCode() dies on "152.0.1-3"), pass NEONWOLF_TREE, glob JDK/NDK paths instead of hardcoding - scripts/build-android.sh: derive tree from ./version + ./release, glob toolchain dirs (works on CI runners), NEONWOLF_BUILD_JOBS knob - .github/workflows/android-build.yml: manual dispatch or push to android/**; free disk, add swap, make fetch/dir, mach bootstrap mobile_android, make build-android, upload APKs + build log as artifacts - docs/ANDROID.md: reflect CI-first build reality Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011MVEMssXqLRUiqjkHgXxfL --- .github/workflows/android-build.yml | 105 +++++++++++++++++++++ .gitignore | 3 + Makefile | 34 +++++++ assets/android/neonwolf-geckoview-prefs.js | 98 +++++++++++++++++++ assets/mozconfig.android | 54 +++++++++++ assets/patches.txt | 1 + docs/ANDROID.md | 88 +++++++++++++++++ patches/android/fenix-neonwolf-appid.patch | 57 +++++++++++ scripts/build-android.sh | 53 +++++++++++ scripts/neonwolf-patches.py | 17 ++++ scripts/package-android-apk.sh | 38 ++++++++ 11 files changed, 548 insertions(+) create mode 100644 .github/workflows/android-build.yml create mode 100644 assets/android/neonwolf-geckoview-prefs.js create mode 100644 assets/mozconfig.android create mode 100644 docs/ANDROID.md create mode 100644 patches/android/fenix-neonwolf-appid.patch create mode 100755 scripts/build-android.sh create mode 100755 scripts/package-android-apk.sh diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml new file mode 100644 index 0000000..20b7391 --- /dev/null +++ b/.github/workflows/android-build.yml @@ -0,0 +1,105 @@ +name: Android build + +# Compiles Neonwolf for Android (full-source GeckoView + Fenix, arm64) on a +# hosted runner and uploads the APKs as artifacts. This exists because local +# builds OOM the dev box: rustc peaked at ~17GB RSS and the kernel oom-killer +# hung the machine (2026-07-10). Hosted runners have 16GB RAM — the swapfile +# step below absorbs the rustc peak — and the job fits the 6h limit. +# +# Trigger: manual dispatch, or push to an android/** branch. + +on: + workflow_dispatch: + push: + branches: ['android/**'] + +concurrency: + group: android-build-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-arm64: + name: GeckoView + Fenix arm64 APK + runs-on: ubuntu-latest + timeout-minutes: 360 + steps: + - name: Checkout (with settings submodule) + uses: actions/checkout@v4 + with: + submodules: recursive + persist-credentials: false + + - name: Free disk space (drop preinstalled toolchains we don't use) + run: | + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \ + /usr/local/.ghcup /opt/hostedtoolcache/CodeQL + sudo docker image prune --all --force >/dev/null 2>&1 || true + df -h / + + - name: Add 16GB swap (single rustc crate peaks ~17GB) + run: | + sudo fallocate -l 16G /mnt/swapfile + sudo chmod 600 /mnt/swapfile + sudo mkswap /mnt/swapfile + sudo swapon /mnt/swapfile + free -h + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + make python3 python3-pip curl gnupg unzip zip patch xz-utils tar rsync + + - name: Ensure rustup (runner images no longer ship Rust) + run: | + if ! command -v rustup >/dev/null 2>&1; then + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + fi + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + + - name: Read pinned Firefox version + id: ver + run: | + echo "version=$(cat version)" >> "$GITHUB_OUTPUT" + echo "release=$(cat release)" >> "$GITHUB_OUTPUT" + + - name: Cache Firefox source tarball + uses: actions/cache@v4 + with: + path: firefox-${{ steps.ver.outputs.version }}.source.tar.xz + key: ff-src-${{ steps.ver.outputs.version }} + + - name: Fetch Firefox source (curl + gpg verify) + run: make fetch + + - name: Extract + apply all patches + Neonwolf overlay (make dir) + run: make dir + + - name: Bootstrap Android toolchain (mach bootstrap) + run: | + cd neonwolf-${{ steps.ver.outputs.version }}-${{ steps.ver.outputs.release }} + ./mach --no-interactive bootstrap --application-choice=mobile_android + rustup target add aarch64-linux-android + + - name: Build (make build-android) + run: make build-android + + - name: Collect APKs + run: | + make package-android + ls -lh dist-android/ + + - name: Upload APKs + uses: actions/upload-artifact@v4 + with: + name: neonwolf-android-arm64-${{ steps.ver.outputs.version }}-${{ steps.ver.outputs.release }} + path: dist-android/*.apk + if-no-files-found: error + + - name: Upload build log + if: always() + uses: actions/upload-artifact@v4 + with: + name: android-build-log + path: android-build.log + if-no-files-found: ignore diff --git a/.gitignore b/.gitignore index abb3d59..8dde101 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ /neonwolf-* /docker-dist *.log +*.nohup.out +android-apk-list.txt +/dist-android patchfail.out scripts/__pycache__/ __pycache__/ diff --git a/Makefile b/Makefile index 12d990d..023aa48 100644 --- a/Makefile +++ b/Makefile @@ -203,6 +203,40 @@ package : run : (cd $(lw_source_dir) && ./mach run) +# --- Android (local full-source GeckoView + Fenix; never artifact mode) --- +# Produces arm64 APK under $(lw_source_dir)/obj-android. Does not push/commit. +# Requires: Android SDK/NDK under ~/.mozbuild, rustup target aarch64-linux-android. +bootstrap-android : $(lw_source_dir) + (cd $(lw_source_dir) && ./mach --no-interactive bootstrap --application-choice=mobile_android) || true + rustup target add aarch64-linux-android x86_64-linux-android || true + mkdir -p $$HOME/.mozbuild/android-device/avd + +build-android : $(lw_source_dir) + cp -v assets/mozconfig.android $(lw_source_dir)/mozconfig + @grep -q 'with-android-sdk' $(lw_source_dir)/mozconfig || \ + printf '\nac_add_options --with-android-sdk=%s\nac_add_options --with-android-ndk=%s\n' \ + "$$HOME/.mozbuild/android-sdk-linux" \ + "$$(ls -d $$HOME/.mozbuild/android-ndk-* 2>/dev/null | sort -V | tail -1)" >> $(lw_source_dir)/mozconfig + # Android Gradle computeVersionCode() parseInts each dotted part and dies on + # "$(version)-$(release)" — bake the pure Firefox version for Android builds. + # (A desktop rebuild in this tree shows plain $(version) until the next `make dir`.) + echo $(version) > $(lw_source_dir)/browser/config/version.txt + echo $(version) > $(lw_source_dir)/browser/config/version_display.txt + # Inject Neonwolf GeckoView prefs if not already present in the tree + @if ! grep -q 'Neonwolf Android privacy defaults' $(lw_source_dir)/mobile/android/app/geckoview-prefs.js 2>/dev/null; then \ + cat assets/android/neonwolf-geckoview-prefs.js >> $(lw_source_dir)/mobile/android/app/geckoview-prefs.js; \ + fi + NEONWOLF_TREE=$(abspath $(lw_source_dir)) \ + ANDROID_AVD_PATH=$$HOME/.mozbuild/android-device/avd \ + ANDROID_HOME=$$HOME/.mozbuild/android-sdk-linux \ + bash scripts/build-android.sh + +package-android : + @find $(lw_source_dir)/obj-android -name '*.apk' 2>/dev/null | tee android-apk-list.txt + @mkdir -p dist-android + @find $(lw_source_dir)/obj-android -name '*arm64*.apk' -exec cp -v {} dist-android/ \; 2>/dev/null || true + @find $(lw_source_dir)/obj-android -name 'fenix*.apk' -exec cp -v {} dist-android/ \; 2>/dev/null || true + @ls -lh dist-android 2>/dev/null || echo "No APKs yet — build still running or failed; see android-build.log" check-patchfail: sh -c "./scripts/check-patchfail.sh" > patchfail.out diff --git a/assets/android/neonwolf-geckoview-prefs.js b/assets/android/neonwolf-geckoview-prefs.js new file mode 100644 index 0000000..26cec4b --- /dev/null +++ b/assets/android/neonwolf-geckoview-prefs.js @@ -0,0 +1,98 @@ +// === Neonwolf Android privacy defaults === +// Appended/injected into mobile/android/app/geckoview-prefs.js at patch time. +// Mirrors the Gecko-shared surface of assets/neonwolf.overrides.cfg + critical +// LibreWolf baseline items that Fenix would otherwise leave open. +// Prefer pref() here (static defaults). Fenix must not clobber these on start. + +// --- Branding / support --- +pref("app.support.baseURL", "https://github.com/neon798/neonwolf/"); +pref("app.releaseNotesURL", "https://github.com/neon798/neonwolf/releases/"); + +// --- Native uBO / content-classifier (must init on GV) --- +pref("privacy.trackingprotection.content.protection.enabled", true); +pref("privacy.trackingprotection.content.protection.list_names", "easylist,easyprivacy,peterlowe,ubo-filters,ubo-badware,ubo-privacy,ubo-quickfixes,adguard-mobile,adguard-base,adguard-tracking,neonwolf-extra"); +pref("privacy.trackingprotection.defer_annotation_enabled", true); +pref("privacy.trackingprotection.content.network_cancel.enabled", false); +pref("privacy.trackingprotection.ubo.cosmetic.enabled", true); +pref("privacy.trackingprotection.ubo.scriptlet.enabled", false); +pref("privacy.trackingprotection.enabled", true); +pref("privacy.trackingprotection.pbmode.enabled", true); +pref("privacy.trackingprotection.socialtracking.enabled", true); +pref("privacy.trackingprotection.fingerprinting.enabled", true); +pref("privacy.trackingprotection.cryptomining.enabled", true); + +// Live list refresh (same contract as desktop) +pref("neonwolf.shields.lists.autoRefresh", true); +pref("neonwolf.shields.lists.refreshIntervalHours", 24); +pref("neonwolf.shields.lists.refreshNow", false); +pref("neonwolf.shields.lists.lastRefresh", 0); +pref("neonwolf.shields.lists.subscriptions", ""); + +// --- FPP-forward farbling (RFP off) --- +pref("privacy.resistFingerprinting", false); +pref("privacy.fingerprintingProtection", true); +pref("privacy.fingerprintingProtection.remoteOverrides.enabled", false); +pref("privacy.fingerprintingProtection.overrides", "+WebGLRandomization,+WebGLRenderInfo,+NavigatorHWConcurrency,+AudioContext,+AudioSampleRate,+FontVisibilityBaseSystem,+ReduceTimerPrecision"); +pref("privacy.spoof_english", 2); +pref("layout.css.prefers-color-scheme.content-override", 2); + +// --- DNS-over-HTTPS Mullvad TRR-only --- +pref("network.trr.mode", 3); +pref("network.trr.uri", "https://base.dns.mullvad.net/dns-query"); +pref("network.trr.default_provider_uri", "https://base.dns.mullvad.net/dns-query"); +pref("network.trr.wait-for-portal", true); +pref("network.trr.skip-address-validation", true); + +// --- Credential / autofill: remove, don't manage --- +pref("signon.rememberSignons", false); +pref("signon.autofillForms", false); +pref("extensions.formautofill.addresses.enabled", false); +pref("extensions.formautofill.creditCards.enabled", false); +pref("extensions.formautofill.addresses.supported", "off"); +pref("extensions.formautofill.creditCards.supported", "off"); + +// --- Permissions default block --- +pref("permissions.default.geo", 2); +pref("permissions.default.camera", 2); +pref("permissions.default.microphone", 2); +pref("permissions.default.desktop-notification", 2); + +// --- Extra hardening --- +pref("browser.send_pings", false); +pref("network.http.referer.defaultPolicy", 0); +pref("dom.security.https_only_mode", true); +pref("dom.security.mixed_content.block_active_content", true); +pref("webgl.disabled", false); +pref("device.sensors.enabled", false); +pref("dom.battery.enabled", false); +pref("dom.gamepad.enabled", false); +pref("dom.netinfo.enabled", false); +pref("media.peerconnection.ice.no_host", true); +pref("privacy.globalprivacycontrol.enabled", true); +pref("privacy.globalprivacycontrol.functionality.enabled", true); +pref("network.cookie.cookieBehavior", 5); + +// --- Safe Browsing: no Google phone-home (malware via uBO lists) --- +pref("browser.safebrowsing.malware.enabled", false); +pref("browser.safebrowsing.phishing.enabled", false); +pref("browser.safebrowsing.downloads.enabled", false); +pref("browser.safebrowsing.provider.google4.enabled", false); +pref("browser.safebrowsing.provider.google5.enabled", false); +pref("browser.safebrowsing.provider.google5.updateURL", ""); +pref("browser.safebrowsing.provider.google5.gethashURL", ""); + +// --- Telemetry / experiments off (belt + suspenders with Lite) --- +pref("toolkit.telemetry.enabled", false); +pref("toolkit.telemetry.unified", false); +pref("datareporting.healthreport.uploadEnabled", false); +pref("datareporting.policy.dataSubmissionEnabled", false); +pref("app.shield.optoutstudies.enabled", false); +pref("browser.discovery.enabled", false); +pref("nimbus.remote-settings.enabled", false); + +// --- Clear on shutdown defaults (Fenix also has its own clear-on-exit) --- +pref("privacy.clearOnShutdown_v2.cookiesAndStorage", true); + +// RemoteSettings dump allowlist (LibreWolf/Neonwolf rs-blocker) +pref("librewolf.services.settings.allowedCollectionsFromDump", "main/devtools-devices,main/devtools-compatibility-browsers,main/search-config-icons,main/search-config-v2,main/search-config-overrides-v2,main/content-classifier-lists"); +pref("librewolf.services.settings.allowedCollections", "security-state/intermediates,security-state/onecrl,security-state/cert-revocations,security-state/message-signatures,main/anti-tracking-url-decoration,main/query-stripping,main/url-parser-default-unknown-schemes-interventions,main/partitioning-exempt-urls,main/url-classifier-skip-urls,main/fingerprinting-protection-overrides,main/translations-models,main/translations-wasm"); diff --git a/assets/mozconfig.android b/assets/mozconfig.android new file mode 100644 index 0000000..790e202 --- /dev/null +++ b/assets/mozconfig.android @@ -0,0 +1,54 @@ +# Neonwolf Android / GeckoView — full source build (NOT artifact mode). +# Artifact mode would pull stock Mozilla Gecko and drop native uBO / content-classifier. +# Keep desktop mozconfig untouched; this file is copied to the tree as mozconfig +# only for Android builds (see Makefile android targets). + +# Separate objdir so we never clobber the desktop Linux build. +mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-android + +ac_add_options --enable-project=mobile/android + +# arm64 first for physical devices; also build x86_64 for emulator later via +# a second mozconfig if needed. Default host-arch is fine for emulator smoke; +# pin aarch64 for phone APKs. +ac_add_options --target=aarch64 + +# GeckoView Lite: no Glean embedded in GV (privacy). +ac_add_options --enable-geckoview-lite + +# End product: Fenix shell. For earliest engine smoke use: +# ac_add_options --enable-android-subproject=geckoview_example +ac_add_options --enable-android-subproject=fenix +# Allow attach/debug on release-optimized GV (local night builds) +ac_add_options --enable-android-debuggable + +ac_add_options --disable-crashreporter +ac_add_options --disable-debug +ac_add_options --disable-tests +ac_add_options --disable-updater +ac_add_options --disable-cargo-incremental +ac_add_options --enable-hardening +ac_add_options --enable-optimize +ac_add_options --enable-release +ac_add_options --enable-rust-simd +ac_add_options --enable-bootstrap + +ac_add_options --with-app-name=neonwolf + +# Branding: desktop branding path may not apply 1:1 on mobile; Fenix branding +# is patched under mobile/android. Keep name identity here for Gecko strings. +# ac_add_options --with-branding=mobile/android/branding/neonwolf + +export MOZ_TELEMETRY_REPORTING= +export MOZ_REQUIRE_SIGNING= +export MOZILLA_OFFICIAL=1 + +mk_add_options MOZ_CRASHREPORTER=0 +mk_add_options MOZ_DATA_REPORTING=0 +mk_add_options MOZ_SERVICES_HEALTHREPORT=0 +mk_add_options MOZ_TELEMETRY_REPORTING=0 + +# Toolchain roots (mach bootstrap installs these under ~/.mozbuild). +# Override if your SDK/NDK live elsewhere. +# ac_add_options --with-android-sdk=$HOME/.mozbuild/android-sdk-linux +# ac_add_options --with-android-ndk=$HOME/.mozbuild/android-ndk-r29 diff --git a/assets/patches.txt b/assets/patches.txt index 7107eef..21c2ff9 100644 --- a/assets/patches.txt +++ b/assets/patches.txt @@ -62,3 +62,4 @@ patches/vendor-name.patch patches/webgl-permission.patch patches/windows-theming-bug.patch patches/xdg-dir.patch +patches/android/fenix-neonwolf-appid.patch diff --git a/docs/ANDROID.md b/docs/ANDROID.md new file mode 100644 index 0000000..410a101 --- /dev/null +++ b/docs/ANDROID.md @@ -0,0 +1,88 @@ +# Neonwolf for Android — build notes + +**Status (2026-07-10):** builds happen on **GitHub Actions** +(`.github/workflows/android-build.yml` — manual dispatch or push to an +`android/**` branch; APKs land as run artifacts). Local full-source builds +**OOM this machine**: a single rustc crate peaks ~17GB RSS, and with the qemu +VM resident the kernel oom-killer hangs the box (observed 2026-07-10 02:56, +`journalctl -k`). If building locally anyway: stop the VM first and set +`NEONWOLF_BUILD_JOBS=2`. + +## What this is + +Android port of Neonwolf: **GeckoView (full source, Neonwolf-patched toolkit)** + **Fenix shell**, package id `org.neonwolf.browser` (debug: `org.neonwolf.browser.debug`). + +Not artifact mode. Not WebView. Not “install uBO as extension.” Native content-classifier / UBONetFilter rides shared `toolkit/`. + +## Prerequisites + +```sh +# From patched tree after desktop make dir +cd neonwolf-152.0.1-3 +./mach --no-interactive bootstrap --application-choice=mobile_android +rustup target add aarch64-linux-android +mkdir -p ~/.mozbuild/android-device/avd # skip broken AVD bootstrap +``` + +SDK: `~/.mozbuild/android-sdk-linux` (platforms android-37.0, build-tools 37.0.0) +NDK: `~/.mozbuild/android-ndk-r29` +JDK: `~/.mozbuild/jdk/jdk-17.0.18+8` (Gradle needs 17; system JDK 26 is too new) + +## Build + +```sh +# From repo root +make build-android # or: scripts/build-android.sh +# Log: android-build.log +make package-android # copy APKs to dist-android/ +``` + +Objdir: `neonwolf-152.0.1-3/obj-android` (does not clobber desktop `obj-x86_64-pc-linux-gnu`). + +## Privacy prefs (Android) + +Injected into `mobile/android/app/geckoview-prefs.js` from +`assets/android/neonwolf-geckoview-prefs.js`: + +- Native adblock enable + list names + defer annotation +- RS dump allowlist including `main/content-classifier-lists` +- FPP-forward, RFP off +- Mullvad TRR-only DoH +- Safe Browsing off +- Telemetry/Nimbus off +- Autofill/passwords off + +## How the Android delta is captured (all reproducible via `make dir` + `make build-android`) + +- Fenix `applicationId` → `org.neonwolf` + `.browser` / `.browser.debug` and + `app_name` → Neonwolf: `patches/android/fenix-neonwolf-appid.patch` + (wired into `assets/patches.txt`, applied by `make dir`). +- GeckoView prefs append: done by `scripts/neonwolf-patches.py` during + `make dir` (from `assets/android/neonwolf-geckoview-prefs.js`), with a + belt-and-suspenders re-check in `make build-android`. +- `mozconfig` → `assets/mozconfig.android` (copied by `make build-android`). +- **`browser/config/version.txt` must be pure `152.0.1`** (not `152.0.1-3`): + desktop bakes `-release` into version.txt; Android Gradle + `computeVersionCode()` parseInts each dotted part and dies on `"1-3"`. + `make build-android` now normalizes both version files automatically (a + desktop rebuild in the same tree shows the plain version until the next + `make dir`). Full Neonwolf build id stays in pref `neonwolf.version.full`. + + +## Verify after install + +```sh +adb install -r dist-android/*.apk +adb logcat | rg -i 'UBONetFilter|ContentClassifier|neonwolf' +# about:config → check network.trr.mode=3, fingerprintingProtection=true, +# privacy.trackingprotection.content.protection.enabled=true +``` + +## Honest gaps for morning tech-preview + +- Full Fenix liberate (strip Contile/Nimbus/telemetry UI) not done yet +- Shields mobile UI not done — engine prefs only +- Emulator system image download flaky; physical arm64 preferred +- Scorecard harness not yet run on device + +See plan: capability matrix still applies; this night’s bar is **installable APK with Gecko prefs + native engine compiled in**. diff --git a/patches/android/fenix-neonwolf-appid.patch b/patches/android/fenix-neonwolf-appid.patch new file mode 100644 index 0000000..f36b8e6 --- /dev/null +++ b/patches/android/fenix-neonwolf-appid.patch @@ -0,0 +1,57 @@ +--- a/mobile/android/fenix/app/build.gradle ++++ b/mobile/android/fenix/app/build.gradle +@@ -62,7 +62,7 @@ + arg("room.schemaLocation", "$projectDir/schemas".toString()) + } + +- applicationId "org.mozilla" ++ applicationId "org.neonwolf" + minSdk config.minSdkVersion + targetSdk config.targetSdkVersion + versionCode 1 +@@ -123,12 +123,12 @@ + debug { + shrinkResources = false + minifyEnabled = false +- applicationIdSuffix ".fenix.debug" ++ applicationIdSuffix ".browser.debug" + resValue "bool", "IS_DEBUG", "true" + pseudoLocalesEnabled = true + } + nightly releaseTemplate >> { +- applicationIdSuffix ".fenix" ++ applicationIdSuffix ".browser" + buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true" + def deepLinkSchemeValue = "fenix-nightly" + buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\"" +@@ -174,7 +174,7 @@ + initWith buildTypes.nightly + shrinkResources = false + minifyEnabled = false +- applicationIdSuffix ".fenix" ++ applicationIdSuffix ".browser" + signingConfig = signingConfigs.debug + debuggable false + buildConfigField "boolean", "IS_BENCHMARK_BUILD", "true" +--- a/mobile/android/fenix/app/src/main/res/values/static_strings.xml ++++ b/mobile/android/fenix/app/src/main/res/values/static_strings.xml +@@ -4,8 +4,8 @@ + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +- Firefox Fenix +- Firefox ++ Neonwolf ++ Neonwolf + + + LeakCanary +@@ -229,7 +229,7 @@ + Profiler active + Profiling is active. Tap to stop profiler + Profiler Settings +- Firefox ++ Neonwolf + Profiler is currently running + Recommended preset for profiling Firefox + Graphics diff --git a/scripts/build-android.sh b/scripts/build-android.sh new file mode 100755 index 0000000..60b9d47 --- /dev/null +++ b/scripts/build-android.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Local Neonwolf Android full-source build. Does not push/commit anywhere. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +TREE="${NEONWOLF_TREE:-$ROOT/neonwolf-$(cat "$ROOT/version")-$(cat "$ROOT/release")}" +LOG="${ROOT}/android-build.log" + +# mach bootstrap installs toolchains under ~/.mozbuild with versioned dirs — +# glob instead of hardcoding so this works on CI runners too. +if [ -z "${JAVA_HOME:-}" ]; then + JAVA_HOME="$(ls -d "$HOME"/.mozbuild/jdk/jdk-17* 2>/dev/null | sort -V | tail -1)" +fi +export JAVA_HOME +export ANDROID_HOME="${ANDROID_HOME:-$HOME/.mozbuild/android-sdk-linux}" +export ANDROID_SDK_ROOT="$ANDROID_HOME" +if [ -z "${ANDROID_NDK_HOME:-}" ]; then + ANDROID_NDK_HOME="$(ls -d "$HOME"/.mozbuild/android-ndk-* 2>/dev/null | sort -V | tail -1)" +fi +export ANDROID_NDK_HOME +# Dummy AVD dir so configure does not try (and fail) to create an emulator image. +export ANDROID_AVD_PATH="${ANDROID_AVD_PATH:-$HOME/.mozbuild/android-device/avd}" +mkdir -p "$ANDROID_AVD_PATH" +CMDLINE_TOOLS="$(ls -d "$ANDROID_HOME"/cmdline-tools/*/bin 2>/dev/null | sort -V | tail -1)" +export PATH="$JAVA_HOME/bin:$ANDROID_HOME/platform-tools${CMDLINE_TOOLS:+:$CMDLINE_TOOLS}:$PATH" + +# Prefer sccache if present +if command -v sccache >/dev/null 2>&1 || [ -x "$HOME/.mozbuild/sccache/sccache" ]; then + export RUSTC_WRAPPER="${RUSTC_WRAPPER:-sccache}" + export CCACHE="${CCACHE:-sccache}" +fi + +cd "$TREE" + +echo "=== Neonwolf Android build $(date -Is) ===" | tee -a "$LOG" +echo "TREE=$TREE JAVA_HOME=$JAVA_HOME" | tee -a "$LOG" +echo "mozconfig:" | tee -a "$LOG" +cat mozconfig | tee -a "$LOG" +echo "=== configure/build ===" | tee -a "$LOG" + +# Full build (configure + compile + package fenix). Resume-safe. +# NEONWOLF_BUILD_JOBS caps parallelism (local box OOMs unthrottled; CI uses all cores). +./mach build -j"${NEONWOLF_BUILD_JOBS:-$(nproc)}" 2>&1 | tee -a "$LOG" +echo "=== mach build exit: $? ===" | tee -a "$LOG" + +# Package / find APKs +./mach package 2>&1 | tee -a "$LOG" || true + +echo "=== APK search ===" | tee -a "$LOG" +find obj-android -name '*.apk' 2>/dev/null | tee -a "$LOG" || true +find . -path './obj-android/*' -name '*-arm64*.apk' 2>/dev/null | head -20 | tee -a "$LOG" || true + +echo "=== done $(date -Is) ===" | tee -a "$LOG" diff --git a/scripts/neonwolf-patches.py b/scripts/neonwolf-patches.py index 7efe71a..2dd0f9c 100644 --- a/scripts/neonwolf-patches.py +++ b/scripts/neonwolf-patches.py @@ -157,6 +157,23 @@ def neonwolf_patches(): require_file('../assets/adblock/easylist.txt', 'adblock filter lists') exec('python3 ../scripts/gen-adblock-dump.py ../assets/adblock .') + # Android: append Neonwolf GeckoView privacy defaults (native blocker, FPP, + # Mullvad TRR, RS dump allowlist for content-classifier-lists). Desktop + # uses autoconfig (librewolf.cfg + overrides); GV uses geckoview-prefs.js. + gv_prefs = 'mobile/android/app/geckoview-prefs.js' + nw_gv_prefs = '../assets/android/neonwolf-geckoview-prefs.js' + if os.path.isfile(gv_prefs) and os.path.isfile(nw_gv_prefs): + with open(gv_prefs, 'r') as f: + existing = f.read() + if 'Neonwolf Android privacy defaults' not in existing and '=== Neonwolf Android' not in existing: + with open(gv_prefs, 'a') as f: + f.write('\n') + with open(nw_gv_prefs, 'r') as src: + f.write(src.read()) + print('appended: Neonwolf GeckoView prefs -> {}'.format(gv_prefs)) + else: + print('skip: Neonwolf GeckoView prefs already present') + # read lines of .txt file into 'patches' with open('../assets/patches.txt'.format(version), "r") as f: for line in f.readlines(): diff --git a/scripts/package-android-apk.sh b/scripts/package-android-apk.sh new file mode 100755 index 0000000..064210f --- /dev/null +++ b/scripts/package-android-apk.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Collect Neonwolf Android APKs from obj-android into dist-android/ (local only). +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +TREE="${NEONWOLF_TREE:-$ROOT/neonwolf-152.0.1-3}" +OBJ="${TREE}/obj-android" +OUT="${ROOT}/dist-android" +mkdir -p "$OUT" + +echo "Searching for APKs under $OBJ ..." +mapfile -t APKS < <(find "$OBJ" -type f -name '*.apk' 2>/dev/null | sort) +if [ ${#APKS[@]} -eq 0 ]; then + echo "No APKs found yet. Is the build still running? See android-build.log" + exit 1 +fi + +for apk in "${APKS[@]}"; do + base="$(basename "$apk")" + # Prefer named copies for the night tech-preview + case "$base" in + *fenix*|*browser*|*geckoview*|*neonwolf*) + cp -v "$apk" "$OUT/$base" + ;; + *) + cp -v "$apk" "$OUT/$base" + ;; + esac +done + +# Symlink / copy a stable name for the primary arm64 debug Fenix if present +PRIMARY=$(find "$OUT" -name '*arm64*.apk' -o -name '*debug*.apk' 2>/dev/null | head -1 || true) +if [ -n "${PRIMARY:-}" ]; then + cp -f "$PRIMARY" "$OUT/neonwolf-android-arm64-techpreview.apk" + echo "Primary: $OUT/neonwolf-android-arm64-techpreview.apk" +fi + +ls -lh "$OUT" +echo "Install: adb install -r $OUT/neonwolf-android-arm64-techpreview.apk" From fb669d04e7ca4131e6a0c6023372aa42d91f7b88 Mon Sep 17 00:00:00 2001 From: neon798 <209852807+neon798@users.noreply.github.com> Date: Fri, 10 Jul 2026 07:48:14 -0600 Subject: [PATCH 2/4] fix(android-ci): replace the runner's preexisting active swapfile The ubuntu-latest image ships an active 4GB /mnt/swapfile; fallocate on it fails with "Text file busy". swapoff + remove before creating the 16GB one. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011MVEMssXqLRUiqjkHgXxfL --- .github/workflows/android-build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index 20b7391..1dbabe4 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -38,6 +38,9 @@ jobs: - name: Add 16GB swap (single rustc crate peaks ~17GB) run: | + # The runner image ships an active 4GB /mnt/swapfile — replace it. + sudo swapoff -a || true + sudo rm -f /mnt/swapfile sudo fallocate -l 16G /mnt/swapfile sudo chmod 600 /mnt/swapfile sudo mkswap /mnt/swapfile From 07faf7f3e88c88224336193ab6bda00493a481cc Mon Sep 17 00:00:00 2001 From: neon798 <209852807+neon798@users.noreply.github.com> Date: Fri, 10 Jul 2026 07:55:39 -0600 Subject: [PATCH 3/4] fix(android-ci): export resolved sccache path, not the bare name configure execs RUSTC_WRAPPER directly; mach bootstrap installs sccache at ~/.mozbuild/sccache/sccache which is not on PATH, so the bare "sccache" died with FileNotFoundError on the runner. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011MVEMssXqLRUiqjkHgXxfL --- scripts/build-android.sh | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/scripts/build-android.sh b/scripts/build-android.sh index 60b9d47..9cbc9cd 100755 --- a/scripts/build-android.sh +++ b/scripts/build-android.sh @@ -24,10 +24,15 @@ mkdir -p "$ANDROID_AVD_PATH" CMDLINE_TOOLS="$(ls -d "$ANDROID_HOME"/cmdline-tools/*/bin 2>/dev/null | sort -V | tail -1)" export PATH="$JAVA_HOME/bin:$ANDROID_HOME/platform-tools${CMDLINE_TOOLS:+:$CMDLINE_TOOLS}:$PATH" -# Prefer sccache if present -if command -v sccache >/dev/null 2>&1 || [ -x "$HOME/.mozbuild/sccache/sccache" ]; then - export RUSTC_WRAPPER="${RUSTC_WRAPPER:-sccache}" - export CCACHE="${CCACHE:-sccache}" +# Prefer sccache if present — export the RESOLVED path: configure execs +# RUSTC_WRAPPER directly, and mach bootstrap's copy is not on PATH. +SCCACHE_BIN="$(command -v sccache 2>/dev/null || true)" +if [ -z "$SCCACHE_BIN" ] && [ -x "$HOME/.mozbuild/sccache/sccache" ]; then + SCCACHE_BIN="$HOME/.mozbuild/sccache/sccache" +fi +if [ -n "$SCCACHE_BIN" ]; then + export RUSTC_WRAPPER="${RUSTC_WRAPPER:-$SCCACHE_BIN}" + export CCACHE="${CCACHE:-$SCCACHE_BIN}" fi cd "$TREE" From 5a2b682c13e3072f3fa93ee827feee808bdeb140 Mon Sep 17 00:00:00 2001 From: neon798 <209852807+neon798@users.noreply.github.com> Date: Fri, 10 Jul 2026 09:59:53 -0600 Subject: [PATCH 4/4] fix(android-ci): assemble the Fenix APK explicitly + collect its outputs Run 3 proved GeckoView + native uBO compiles on the runner (1h54m, swap absorbed the rustc peak) but produced no Fenix APK: mach build only builds GeckoView. Add the documented `mach gradle fenix:assembleDebug` step (mobile/android/docs/fenix.rst), point package-android at the fenix gradle output dir, and drop --enable-geckoview-lite (Fenix expects the full GV artifact; telemetry dies via prefs + liberation instead). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011MVEMssXqLRUiqjkHgXxfL --- Makefile | 4 ++-- assets/mozconfig.android | 6 ++++-- scripts/build-android.sh | 4 ++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 023aa48..59ba928 100644 --- a/Makefile +++ b/Makefile @@ -234,8 +234,8 @@ build-android : $(lw_source_dir) package-android : @find $(lw_source_dir)/obj-android -name '*.apk' 2>/dev/null | tee android-apk-list.txt @mkdir -p dist-android - @find $(lw_source_dir)/obj-android -name '*arm64*.apk' -exec cp -v {} dist-android/ \; 2>/dev/null || true - @find $(lw_source_dir)/obj-android -name 'fenix*.apk' -exec cp -v {} dist-android/ \; 2>/dev/null || true + @find $(lw_source_dir)/obj-android/gradle/build/mobile/android/fenix -name '*.apk' -not -name '*androidTest*' -exec cp -v {} dist-android/ \; 2>/dev/null || true + @find $(lw_source_dir)/obj-android -name '*arm64*.apk' -not -path '*/fenix/*' -exec cp -v {} dist-android/ \; 2>/dev/null || true @ls -lh dist-android 2>/dev/null || echo "No APKs yet — build still running or failed; see android-build.log" check-patchfail: diff --git a/assets/mozconfig.android b/assets/mozconfig.android index 790e202..2fff910 100644 --- a/assets/mozconfig.android +++ b/assets/mozconfig.android @@ -13,8 +13,10 @@ ac_add_options --enable-project=mobile/android # pin aarch64 for phone APKs. ac_add_options --target=aarch64 -# GeckoView Lite: no Glean embedded in GV (privacy). -ac_add_options --enable-geckoview-lite +# NOTE: --enable-geckoview-lite was dropped: Fenix consumes the full GeckoView +# artifact (its Glean integration expects it; lite is for third-party +# embedders). Telemetry is disabled via geckoview-prefs + the Fenix +# liberation work instead. # End product: Fenix shell. For earliest engine smoke use: # ac_add_options --enable-android-subproject=geckoview_example diff --git a/scripts/build-android.sh b/scripts/build-android.sh index 9cbc9cd..025d9fb 100755 --- a/scripts/build-android.sh +++ b/scripts/build-android.sh @@ -48,6 +48,10 @@ echo "=== configure/build ===" | tee -a "$LOG" ./mach build -j"${NEONWOLF_BUILD_JOBS:-$(nproc)}" 2>&1 | tee -a "$LOG" echo "=== mach build exit: $? ===" | tee -a "$LOG" +# mach build only assembles GeckoView — the Fenix app APK needs an explicit +# gradle invocation (mobile/android/docs/fenix.rst). +./mach gradle fenix:assembleDebug 2>&1 | tee -a "$LOG" + # Package / find APKs ./mach package 2>&1 | tee -a "$LOG" || true