build: pin the app's packaging NDK to the Arti revision

AGP runs the NDK's llvm-strip over everything merged into jniLibs, so the
NDK is an APK input and not just a concern for whoever compiles Arti. No
module set ndkVersion, so the app silently used AGP's own default --
28.2.13676358 on AGP 9.4.0 ("Because no explicit NDK was requested, the
default version [...] for this Android Gradle Plugin will be used") --
while tools/arti-build/ANDROID_NDK_VERSION pins 30.0.16248370 and the
committed libarti_android.so are stamped r30/16248370 in
.note.android.ident. The library was built with r30 and stripped with r28,
and on a machine with no NDK installed it shipped unstripped instead: three
different APKs from one source tree, which is what F-Droid's rebuild
verification cannot have. The default also moves with every AGP bump.

:amethyst now reads ndkVersion straight from ANDROID_NDK_VERSION rather
than duplicating it into the version catalog, so the packaging toolchain
and the reproducibility pin cannot drift: bumping the pin (which already
means rebuilding the .so) moves both. Verified by reflecting the resolved
android.ndkVersion out of a configured build: :amethyst = 30.0.16248370.

The other Android modules (:benchmark, :baselineprofile, :nappletHost) keep
AGP's default -- none of them package native libraries, so no strip step
there ever resolves an NDK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ck919Y21reMU6LVrev1QJo
This commit is contained in:
Claude
2026-09-18 14:58:21 +00:00
parent b76401e136
commit c2071ee82c
3 changed files with 40 additions and 0 deletions
+9
View File
@@ -109,6 +109,15 @@ Rust toolchain + the exact Android NDK revision pinned in
`tools/arti-build/ANDROID_NDK_VERSION` for Arti) documented in their READMEs —
they are **not** required to build Amethyst from the committed sources.
The NDK half of that Arti pin does reach the ordinary Android build, though:
`:amethyst` sets `ndkVersion` from `ANDROID_NDK_VERSION`, because AGP strips
the committed `.so` with the NDK's `llvm-strip` while packaging the APK. The
Rust toolchain stays irrelevant, but Studio/AGP will fetch that NDK revision
(`sdkmanager "ndk;$(cat tools/arti-build/ANDROID_NDK_VERSION)"` to pre-install
it). Without any NDK the build still succeeds — AGP warns and packages the
library unstripped, which is fine to run but no longer byte-comparable to a
release APK.
---
## Per-format build commands
+23
View File
@@ -74,6 +74,29 @@ android {
.get()
.toInt()
// Packaging toolchain: AGP runs the NDK's llvm-strip over everything that
// lands in jniLibs — our committed libarti_android.so included — so the
// NDK revision is a build input for the APK, not just for whoever compiles
// Arti. Left unset it silently follows AGP's own default (28.2.13676358 on
// AGP 9.4.0), which moves with every AGP bump and is a different toolchain
// from the one that produced the .so, while a machine with no NDK at all
// packages the library unstripped ("Unable to strip the following
// libraries") — three different APKs from the same source, which is
// exactly what F-Droid's rebuild verification cannot have.
//
// Read straight from the Arti pin rather than copied into the version
// catalog: the two can then never drift, and bumping ANDROID_NDK_VERSION
// (which also means rebuilding the .so) moves the packaging toolchain with
// it. See tools/arti-build/README.md → "Reproducible builds".
ndkVersion =
providers
.fileContents(layout.settingsDirectory.file("tools/arti-build/ANDROID_NDK_VERSION"))
.asText
.orNull
?.trim()
?.takeIf { it.isNotEmpty() }
?: error("tools/arti-build/ANDROID_NDK_VERSION is missing or empty — it pins the NDK that strips src/main/jniLibs")
defaultConfig {
applicationId = "com.vitorpamplona.amethyst"
minSdk =
+8
View File
@@ -49,6 +49,14 @@ committed binary wasn't tampered with. **Five** things have to be fixed:
> pinned output was verified with. `cargo-ndk` only wraps the NDK, so a mismatch
> is a warning rather than an error — but it is the next thing to check if your
> rebuild does not match.
>
> **The app build reads this pin too.** `amethyst/build.gradle.kts` sets
> `ndkVersion` from `ANDROID_NDK_VERSION`, because AGP runs the NDK's
> `llvm-strip` over `src/main/jniLibs/` while packaging — the toolchain that
> strips the library is as much an APK input as the one that compiled it. Unset,
> `ndkVersion` follows AGP's own default (r28 on AGP 9.4.0) and moves with every
> AGP bump. So bumping this file changes what packagers need installed, not only
> what rebuilders need: bump it, rebuild the `.so`, and commit both.
`repro-env.sh` (sourced by both build scripts) also sets `CARGO_INCREMENTAL=0`
and a fixed `SOURCE_DATE_EPOCH` derived from the Arti tag. The size-optimized