build: ship libarti_android.so past AGP's strip step

The Cargo release profile already strips this library -- readelf shows no
.symtab and no .debug_* -- so AGP's `llvm-strip --strip-unneeded` pass has
nothing to remove. It still rewrites the file: llvm-strip rebuilds .comment,
the section holding the rustc / clang / lld version stamps, which changes 273
bytes on arm64-v8a (verified by running the same command over the committed
.so). That made the packaged bytes a function of whichever llvm-strip ran, so
the library inside an APK could never be compared against the committed one
that tools/arti-build reproduces byte-for-byte.

packaging.jniLibs.keepDebugSymbols now excludes it from that step. No size
cost, since there are no symbols left to drop, and

    unzip -p app.apk lib/arm64-v8a/libarti_android.so | sha256sum

can now be checked straight against src/main/jniLibs. Dependency .so files are
still stripped, by the NDK pinned in the previous commit -- which is why that
pin stays.

Verified by reflecting the configured DSL out of the build:
:amethyst ndkVersion = 30.0.16248370, keepDebugSymbols = [**/libarti_android.so].

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 16:58:39 +00:00
parent c2071ee82c
commit 0fc57ef6be
3 changed files with 39 additions and 10 deletions
+11 -8
View File
@@ -109,14 +109,17 @@ 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.
The NDK half of that Arti pin does reach the ordinary Android build, though.
AGP strips every native library it packages with the NDK's `llvm-strip`, so
`:amethyst` sets `ndkVersion` from `ANDROID_NDK_VERSION` — one revision for the
libraries we build and the ones we merge from dependencies. `libarti_android.so`
is then excluded from that strip step (`packaging.jniLibs.keepDebugSymbols`):
the Cargo release profile already stripped it, and llvm-strip would only rewrite
its `.comment` stamps, so skipping the pass costs no size and lets the `.so`
inside an APK be compared byte-for-byte against the committed, independently
reproducible one. The Rust toolchain stays irrelevant either way; Studio/AGP
fetches the pinned NDK on demand, or pre-install it with
`sdkmanager "ndk;$(cat tools/arti-build/ANDROID_NDK_VERSION)"`.
---
+16
View File
@@ -318,6 +318,22 @@ android {
resources {
excludes += listOf("/META-INF/{AL2.0,LGPL2.1}", "**/libscrypt.dylib")
}
jniLibs {
// Reproducible builds, part two: ship the Arti library exactly as
// tools/arti-build produced it. Its Cargo release profile already
// strips it (no .symtab, no .debug_*), so AGP's
// `llvm-strip --strip-unneeded` pass has nothing left to remove — but it
// still rewrites the file: llvm-strip rebuilds .comment, the section that
// records the rustc / clang / lld version stamps, which measurably changes
// 273 bytes on arm64-v8a. That made the packaged bytes a function of
// whichever NDK did the stripping, so the .so in an APK could never be
// compared against the committed, independently reproducible one.
// Excluding it from the strip step costs nothing in size (there are no
// symbols to drop) and makes that comparison exact. Dependency .so files
// are still stripped, with the NDK pinned by ndkVersion above.
keepDebugSymbols += "**/libarti_android.so"
}
}
lint {
+12 -2
View File
@@ -52,11 +52,21 @@ committed binary wasn't tampered with. **Five** things have to be fixed:
>
> **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,
> `llvm-strip` over every native library it packages — the toolchain that strips
> a 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.
>
> **`libarti_android.so` skips that strip step.** The release profile here
> already strips it — no `.symtab`, no `.debug_*` — so `llvm-strip
> --strip-unneeded` has nothing to remove and only rebuilds `.comment`, the
> section carrying the rustc/clang/lld stamps (273 bytes change on arm64-v8a).
> `packaging.jniLibs.keepDebugSymbols` in `amethyst/build.gradle.kts` therefore
> excludes it, which costs no APK size and means the library inside a built APK
> is byte-identical to the one committed in `src/main/jniLibs/`: `unzip -p
> app.apk lib/arm64-v8a/libarti_android.so | sha256sum` can be checked straight
> against the file this script reproduces.
`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