mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
PR #4146 committed libzxingcpp_android.so for all four ABIs but landed it outside both native-library guards main had just built for Arti, so two properties the QR decoder's README claims are not actually true of the APK: * verifyArtiAbis only ever looked for libarti_android.so. An ABI split missing libzxingcpp_android.so — or holding a truncated one, or arm64's copied into x86/ — builds and installs clean, and the scanner then fails to load on that architecture with nothing in the build to catch it. Renamed verifyNativeAbis and driven from a map of committed libraries, so each one is checked on every shipped ABI and the error names the build command to re-run. * keepDebugSymbols excluded only libarti_android.so, so AGP's llvm-strip pass rewrites the QR library on its way into the APK. That makes `unzip -p app.apk lib/<abi>/libzxingcpp_android.so | sha256sum` a function of whoever built the APK rather than of the committed bytes, which is exactly the comparison tools/zxing-cpp-build exists to make possible. The same PR also added tools/zxing-cpp-build/ANDROID_NDK_VERSION as a second copy of the NDK pin.c2071ee82chad just made :amethyst read ndkVersion straight from tools/arti-build/ANDROID_NDK_VERSION rather than duplicate it ("the two can then never drift"), andea8326f759deleted verify-reproducible.sh's private copy of the ABI list for the same reason. build-zxingcpp.sh now reads that one file too and the copy is gone, so a bump moves both native builds and the strip toolchain together. Docs follow: BUILDING.md still described Arti as the only committed .so and named verifyArtiAbis, as did tools/arti-build/README.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0134jvyriixNTHST4WRbbqbX
109 lines
5.9 KiB
Markdown
109 lines
5.9 KiB
Markdown
# zxing-cpp Android Build Tools
|
|
|
|
Custom-built [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp) native library for Amethyst's QR
|
|
scanner. This replaces the prebuilt `io.github.zxing-cpp:android` AAR with the same library built
|
|
from source, the way [`tools/arti-build`](../arti-build) replaces the Guardian Project AAR.
|
|
|
|
## Why custom build?
|
|
|
|
Not for size — the published AAR's libraries are already stripped, and ours come out only slightly
|
|
smaller. **For verifiability.**
|
|
|
|
The AAR ships four `.so` files built by a third party on toolchains we cannot see, and nothing in
|
|
this repository could check them. A QR scanner is a thing you point at a stranger's phone, and the
|
|
binary that parses whatever comes back was one we took on trust. Amethyst already holds
|
|
`libarti_android.so` to a reproducible standard; there was no principled reason for the barcode
|
|
decoder to be exempt.
|
|
|
|
Building it ourselves also means F-Droid, Zapstore or any auditor can rebuild from this tag and
|
|
confirm the committed bytes, instead of being asked to trust a Maven artifact.
|
|
|
|
## Quick start
|
|
|
|
Pre-built `.so` files are committed to `amethyst/src/main/jniLibs/`. You only need to rebuild to
|
|
verify the binaries, bump the zxing-cpp version, or change the build flags.
|
|
|
|
```bash
|
|
./build-zxingcpp.sh # every ABI
|
|
./build-zxingcpp.sh --abi arm64-v8a # one ABI, much faster
|
|
./verify-reproducible.sh # build twice, compare bytes, compare against the commit
|
|
```
|
|
|
|
Prerequisites: `git`, `cmake`, `ninja`, and the exact NDK revision in
|
|
[`tools/arti-build/ANDROID_NDK_VERSION`](../arti-build/ANDROID_NDK_VERSION). That is the
|
|
repo's single NDK pin, not a copy: `build-zxingcpp.sh`, `build-arti.sh` and `:amethyst`'s
|
|
`ndkVersion` all read that one file, so one NDK install serves every native build and the
|
|
three can never drift apart.
|
|
|
|
## Reproducible builds
|
|
|
|
Five things have to be fixed, and each is:
|
|
|
|
| Source of non-determinism | Pinned by |
|
|
|---|---|
|
|
| compiler + linker version | [`tools/arti-build/ANDROID_NDK_VERSION`](../arti-build/ANDROID_NDK_VERSION); `build-zxingcpp.sh` refuses any other revision |
|
|
| upstream source | [`ZXING_CPP_VERSION`](ZXING_CPP_VERSION), cloned at that tag and nothing else |
|
|
| absolute paths baked into `__FILE__`, assertions, debug records | `-ffile-prefix-map` / `-fdebug-prefix-map` in [`repro-env.sh`](repro-env.sh) |
|
|
| timestamps | `SOURCE_DATE_EPOCH`, derived from the pinned tag's commit rather than from build time; `__DATE__`/`__TIME__` redacted |
|
|
| codegen/link ordering keyed on the real build path | canonical build path (`/tmp/amethyst-zxingcpp-build`) |
|
|
|
|
> **Why the NDK is pinned.** clang compiles the C++ and lld links the `.so`, and both stamp their
|
|
> versions into the binary's `.comment` section. Swapping the NDK changes the bytes exactly as
|
|
> swapping compilers would. `build-zxingcpp.sh` reads each candidate directory's
|
|
> `source.properties` and keeps looking until it finds the pinned revision — no wildcards. Picking
|
|
> "some NDK" is precisely how arti's committed binaries ended up built by r25b while its README
|
|
> told everyone to install r27; that lesson is borrowed here rather than re-learned.
|
|
|
|
> **Why the output is re-checked.** After each build the script decodes the library's own
|
|
> `.note.android.ident`, which records the min SDK and the NDK release and build number, and fails
|
|
> if they are not what was asked for. The revision gate checks the *input*; this checks the
|
|
> *output*, which is what catches a stale CMake cache or an overriding environment variable that
|
|
> slipped a different toolchain past the gate.
|
|
|
|
Verified on this machine: two clean builds of `arm64-v8a` produced identical bytes
|
|
(`dec4397c…39dbf`), and `verify-reproducible.sh` confirmed they match the committed library.
|
|
|
|
## What is built
|
|
|
|
`wrappers/android/zxingcpp/src/main/cpp/CMakeLists.txt` from the pinned tag, which pulls in
|
|
zxing-cpp's `core/` and compiles one JNI file. Readers only (`ZXING_WRITERS=OFF`) — Amethyst
|
|
encodes its QR codes with ZXing core on the JVM, and never writes one natively.
|
|
|
|
All four ABIs, unlike arti's two. Tor is an optional feature that can be absent; a QR scanner that
|
|
fails to load is a broken core feature, and the decoder this replaced was pure Java and worked
|
|
everywhere. Dropping an ABI would mean a silent regression on those devices unless the ZXing-Java
|
|
fallback behind `BarcodeDecoder` were wired up too.
|
|
|
|
## The Kotlin half
|
|
|
|
The AAR shipped the `zxingcpp.BarcodeReader` class alongside the `.so`, so dropping the AAR means
|
|
carrying that too: it is vendored verbatim at `amethyst/src/main/java/zxingcpp/BarcodeReader.kt`.
|
|
|
|
Two things there are load-bearing and easy to break:
|
|
|
|
- **The package and class name.** The native library exports
|
|
`Java_zxingcpp_BarcodeReader_readYBuffer`, so moving or renaming the class breaks the JNI lookup
|
|
at runtime with no build error.
|
|
- **`-keep class zxingcpp.** { *; }`** in `amethyst/proguard-rules.pro`. This used to arrive as
|
|
the AAR's consumer rule. Without it R8 renames the class in release builds and the scanner fails
|
|
to start — in release only, which is the worst place to find out.
|
|
|
|
The vendored file keeps its upstream Apache-2.0 header and is excluded from spotless in the root
|
|
`build.gradle.kts`, so our MIT header is never stamped onto it.
|
|
|
|
## Updating zxing-cpp
|
|
|
|
1. Change [`ZXING_CPP_VERSION`](ZXING_CPP_VERSION) to the new tag.
|
|
2. Run `./build-zxingcpp.sh`.
|
|
3. Re-copy `BarcodeReader.kt` from the pinned clone
|
|
(`/tmp/amethyst-zxingcpp-build/.zxing-cpp-source/wrappers/android/zxingcpp/src/main/java/zxingcpp/`)
|
|
so the two halves cannot drift — a Kotlin wrapper from one version against a `.so` from another
|
|
fails at the JNI boundary, not at compile time.
|
|
4. Run `./verify-reproducible.sh` and commit the `.so` files with the hash it reports.
|
|
|
|
## Licensing
|
|
|
|
zxing-cpp is Apache-2.0 — permissive, so linking it into Amethyst's MIT-licensed artifacts is
|
|
fine, and no linking-exception question arises. The vendored Kotlin file carries its upstream
|
|
`SPDX-License-Identifier: Apache-2.0` and copyright line unchanged.
|