mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
build(qr): guard libzxingcpp_android.so like libarti_android.so
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
This commit is contained in:
+31
-21
@@ -89,8 +89,8 @@ cd amethyst
|
|||||||
|
|
||||||
## Generated & vendored artifacts
|
## Generated & vendored artifacts
|
||||||
|
|
||||||
Two build inputs are **generated by tools but committed to the repo**, so a
|
Three build inputs are **generated by tools but committed to the repo**, so a
|
||||||
normal build or release does **not** run either — Gradle just consumes the
|
normal build or release does **not** run any of them — Gradle just consumes the
|
||||||
checked-in output. You only regenerate them under the specific conditions below,
|
checked-in output. You only regenerate them under the specific conditions below,
|
||||||
and each has its own guide:
|
and each has its own guide:
|
||||||
|
|
||||||
@@ -98,34 +98,44 @@ and each has its own guide:
|
|||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| **Material Symbols subset font** | `commonsUI/src/commonMain/composeResources/font/material_symbols_outlined.ttf` | You add/remove a `MaterialSymbol("\uXXXX")` codepoint in `MaterialSymbols.kt`, or bump the upstream font | [`tools/material-symbols-subset/README.md`](tools/material-symbols-subset/README.md) — run `./tools/material-symbols-subset/subset.sh` |
|
| **Material Symbols subset font** | `commonsUI/src/commonMain/composeResources/font/material_symbols_outlined.ttf` | You add/remove a `MaterialSymbol("\uXXXX")` codepoint in `MaterialSymbols.kt`, or bump the upstream font | [`tools/material-symbols-subset/README.md`](tools/material-symbols-subset/README.md) — run `./tools/material-symbols-subset/subset.sh` |
|
||||||
| **Arti (Tor) native libs** | `amethyst/src/main/jniLibs/*.so` | You update the pinned Arti version, change the JNI wrapper, or want to reproduce the binaries | [`tools/arti-build/README.md`](tools/arti-build/README.md) |
|
| **Arti (Tor) native libs** | `amethyst/src/main/jniLibs/*.so` | You update the pinned Arti version, change the JNI wrapper, or want to reproduce the binaries | [`tools/arti-build/README.md`](tools/arti-build/README.md) |
|
||||||
|
| **zxing-cpp (QR decoder) native libs** | `amethyst/src/main/jniLibs/*/libzxingcpp_android.so` | You bump `ZXING_CPP_VERSION`, change the JNI wrapper, or want to reproduce the binaries | [`tools/zxing-cpp-build/README.md`](tools/zxing-cpp-build/README.md) |
|
||||||
|
|
||||||
> **Material Symbols is mandatory after icon changes.** The bundled font is a
|
> **Material Symbols is mandatory after icon changes.** The bundled font is a
|
||||||
> ~210-glyph subset; a new codepoint that isn't in it renders as tofu (□) at
|
> ~210-glyph subset; a new codepoint that isn't in it renders as tofu (□) at
|
||||||
> runtime. Regenerate and commit the `.ttf` alongside the `MaterialSymbols.kt`
|
> runtime. Regenerate and commit the `.ttf` alongside the `MaterialSymbols.kt`
|
||||||
> change. Reusing an existing codepoint needs no regeneration.
|
> change. Reusing an existing codepoint needs no regeneration.
|
||||||
|
|
||||||
Both tools have their own prerequisites (`fonttools`/`brotli` for the font; a
|
Each tool has its own prerequisites (`fonttools`/`brotli` for the font; a Rust
|
||||||
Rust toolchain + the exact Android NDK revision pinned in
|
toolchain for Arti; `cmake` + `ninja` for zxing-cpp; and, for both native
|
||||||
`tools/arti-build/ANDROID_NDK_VERSION` for Arti) documented in their READMEs —
|
builds, the exact Android NDK revision pinned in
|
||||||
they are **not** required to build Amethyst from the committed sources.
|
`tools/arti-build/ANDROID_NDK_VERSION`) documented in their READMEs — none of
|
||||||
|
them are required to build Amethyst from the committed sources.
|
||||||
|
|
||||||
The NDK half of that Arti pin does reach the ordinary Android build, though.
|
That NDK pin is a single file for the whole repo, not a copy per tool:
|
||||||
AGP strips every native library it packages with the NDK's `llvm-strip`, so
|
`build-arti.sh`, `build-zxingcpp.sh` and `:amethyst`'s `ndkVersion` all read it,
|
||||||
`:amethyst` sets `ndkVersion` from `ANDROID_NDK_VERSION` — one revision for the
|
so bumping it moves every native build and the packaging toolchain together and
|
||||||
libraries we build and the ones we merge from dependencies. `libarti_android.so`
|
they cannot drift apart. (It lives under `tools/arti-build/` for history; it is
|
||||||
is then excluded from that strip step (`packaging.jniLibs.keepDebugSymbols`):
|
not Arti's alone.)
|
||||||
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`
|
The NDK half of that pin reaches the ordinary Android build. AGP strips every
|
||||||
inside an APK be compared byte-for-byte against the committed, independently
|
native library it packages with the NDK's `llvm-strip`, so `:amethyst` sets
|
||||||
reproducible one. The Rust toolchain stays irrelevant either way; Studio/AGP
|
`ndkVersion` from `ANDROID_NDK_VERSION` — one revision for the libraries we
|
||||||
fetches the pinned NDK on demand, or pre-install it with
|
build and the ones we merge from dependencies. Both of our own libraries are
|
||||||
|
then excluded from that strip step (`packaging.jniLibs.keepDebugSymbols`):
|
||||||
|
they are already stripped by the pinned toolchain, 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)"`.
|
`sdkmanager "ndk;$(cat tools/arti-build/ANDROID_NDK_VERSION)"`.
|
||||||
|
|
||||||
> **One Arti library per ABI split.** The APK is split four ways (`arm64-v8a`,
|
> **Every ABI split needs its own copy of each library.** The APK is split four
|
||||||
> `x86_64`, `armeabi-v7a`, `x86`) and every split needs its own
|
> ways (`arm64-v8a`, `x86_64`, `armeabi-v7a`, `x86`). A split missing
|
||||||
> `libarti_android.so`; a split without one installs and runs with Tor silently
|
> `libarti_android.so` installs and runs with Tor silently unavailable; one
|
||||||
> unavailable. The `verifyArtiAbis` Gradle task fails the build if the two lists
|
> missing `libzxingcpp_android.so` installs with the QR scanner broken. The
|
||||||
> drift, and names the `build-arti.sh --target=…` to run.
|
> `verifyNativeAbis` Gradle task fails the build if a library's ABI list drifts
|
||||||
|
> from the split list, and names the `build-arti.sh --target=…` /
|
||||||
|
> `build-zxingcpp.sh --abi …` to run — and rejects a file that is not an ELF
|
||||||
|
> built for that architecture, which a missing-file check would pass.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+85
-52
@@ -67,14 +67,27 @@ afterEvaluate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Every ABI we split the APK for, and therefore every ABI that needs its own
|
// Every ABI we split the APK for, and therefore every ABI that needs its own copy of each
|
||||||
// libarti_android.so under src/main/jniLibs/ (see tools/arti-build/). The two
|
// library we build and commit ourselves under src/main/jniLibs/. The lists drifted once: the
|
||||||
// lists drifted once: the splits shipped four ABIs while Arti was built for two,
|
// splits shipped four ABIs while Arti was built for two, so the armeabi-v7a and x86 APKs
|
||||||
// so the armeabi-v7a and x86 APKs installed and ran with the dependencies' native
|
// installed and ran with the dependencies' native libraries all present (secp256k1's JNI ships
|
||||||
// libraries all present (secp256k1's JNI ships every ABI) and Tor alone dead for
|
// every ABI) and Tor alone dead for the life of the install. `verifyNativeAbis` below keeps
|
||||||
// the life of the install. `verifyArtiAbis` below keeps them in step.
|
// them in step.
|
||||||
val shippedAbis = listOf("x86", "x86_64", "arm64-v8a", "armeabi-v7a")
|
val shippedAbis = listOf("x86", "x86_64", "arm64-v8a", "armeabi-v7a")
|
||||||
|
|
||||||
|
// The libraries that guard covers, and how to rebuild one when it is missing. Both are built
|
||||||
|
// from source by tools/ rather than pulled prebuilt, so both can go missing the same way — and
|
||||||
|
// a QR scanner that cannot load is as silently broken on that install as a dead Tor.
|
||||||
|
val committedNativeLibs =
|
||||||
|
mapOf(
|
||||||
|
"libarti_android.so" to { abi: String, triple: String ->
|
||||||
|
"./tools/arti-build/build-arti.sh --target=$triple"
|
||||||
|
},
|
||||||
|
"libzxingcpp_android.so" to { abi: String, _: String ->
|
||||||
|
"./tools/zxing-cpp-build/build-zxingcpp.sh --abi $abi"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.vitorpamplona.amethyst"
|
namespace = "com.vitorpamplona.amethyst"
|
||||||
compileSdk =
|
compileSdk =
|
||||||
@@ -92,10 +105,14 @@ android {
|
|||||||
// libraries") — three different APKs from the same source, which is
|
// libraries") — three different APKs from the same source, which is
|
||||||
// exactly what F-Droid's rebuild verification cannot have.
|
// exactly what F-Droid's rebuild verification cannot have.
|
||||||
//
|
//
|
||||||
// Read straight from the Arti pin rather than copied into the version
|
// Read straight from the pin rather than copied into the version catalog:
|
||||||
// catalog: the two can then never drift, and bumping ANDROID_NDK_VERSION
|
// the two can then never drift, and bumping ANDROID_NDK_VERSION (which also
|
||||||
// (which also means rebuilding the .so) moves the packaging toolchain with
|
// means rebuilding the .so files) moves the packaging toolchain with it.
|
||||||
// it. See tools/arti-build/README.md → "Reproducible builds".
|
// That one file is the repo's only NDK pin -- tools/arti-build/build-arti.sh
|
||||||
|
// and tools/zxing-cpp-build/build-zxingcpp.sh read it too, so every
|
||||||
|
// committed .so is produced and stripped by the same revision. It lives
|
||||||
|
// under tools/arti-build for history; it is not Arti's alone. See
|
||||||
|
// tools/arti-build/README.md → "Reproducible builds".
|
||||||
ndkVersion =
|
ndkVersion =
|
||||||
providers
|
providers
|
||||||
.fileContents(layout.settingsDirectory.file("tools/arti-build/ANDROID_NDK_VERSION"))
|
.fileContents(layout.settingsDirectory.file("tools/arti-build/ANDROID_NDK_VERSION"))
|
||||||
@@ -341,6 +358,17 @@ android {
|
|||||||
// symbols to drop) and makes that comparison exact. Dependency .so files
|
// symbols to drop) and makes that comparison exact. Dependency .so files
|
||||||
// are still stripped, with the NDK pinned by ndkVersion above.
|
// are still stripped, with the NDK pinned by ndkVersion above.
|
||||||
keepDebugSymbols += "**/libarti_android.so"
|
keepDebugSymbols += "**/libarti_android.so"
|
||||||
|
|
||||||
|
// Same guarantee for the QR decoder, for a different reason. Unlike Arti's, this
|
||||||
|
// library is *not* currently rewritten by AGP's pass -- verified by running the
|
||||||
|
// pinned NDK's `llvm-strip --strip-unneeded` over the committed file and getting
|
||||||
|
// identical bytes -- because tools/zxing-cpp-build strips it with that very same
|
||||||
|
// llvm-strip, which makes a second pass idempotent. That idempotence is a property
|
||||||
|
// of one NDK revision, though, and reading it back from the APK should not depend
|
||||||
|
// on a strip pass staying a no-op across bumps. Excluding it makes
|
||||||
|
// `unzip -p app.apk lib/<abi>/libzxingcpp_android.so | sha256sum` match
|
||||||
|
// src/main/jniLibs by construction, at no size cost.
|
||||||
|
keepDebugSymbols += "**/libzxingcpp_android.so"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,20 +409,20 @@ android {
|
|||||||
// only surfaces at System.loadLibrary time on a user's device — where
|
// only surfaces at System.loadLibrary time on a user's device — where
|
||||||
// TorManager's flow swallows the UnsatisfiedLinkError and leaves the status Off
|
// TorManager's flow swallows the UnsatisfiedLinkError and leaves the status Off
|
||||||
// forever. Checked at build time instead, against the same list the splits use.
|
// forever. Checked at build time instead, against the same list the splits use.
|
||||||
val verifyArtiAbis =
|
val verifyNativeAbis =
|
||||||
tasks.register("verifyArtiAbis") {
|
tasks.register("verifyNativeAbis") {
|
||||||
group = "verification"
|
group = "verification"
|
||||||
description = "Checks that every ABI in the APK splits has a libarti_android.so for that architecture."
|
description = "Checks that every ABI in the APK splits has each committed native library, built for that architecture."
|
||||||
|
|
||||||
val jniLibs = file("src/main/jniLibs")
|
val jniLibs = file("src/main/jniLibs")
|
||||||
val abis = shippedAbis
|
val abis = shippedAbis
|
||||||
// Per ABI: the Rust target triple (so a failure names the exact build
|
val libs = committedNativeLibs
|
||||||
// command) and the ELF identity the library must have — 32/64-bit class
|
// Per ABI: the Rust target triple (so an Arti failure names the exact build command) and
|
||||||
// (header byte 4) and e_machine (bytes 18-19, little-endian on every
|
// the ELF identity every library must have — 32/64-bit class (header byte 4) and
|
||||||
// Android ABI we ship). Existence alone is not enough: a truncated file,
|
// e_machine (bytes 18-19, little-endian on every Android ABI we ship). Existence alone is
|
||||||
// an empty placeholder, or arm64's .so copied into x86/ all load as
|
// not enough: a truncated file, an empty placeholder, or arm64's .so copied into x86/ all
|
||||||
// nothing on device, which is the same silent dead Tor this task exists
|
// load as nothing on device, which is the same silent failure this task exists to prevent
|
||||||
// to prevent — and unlike a missing file, those look fine in git.
|
// — and unlike a missing file, those look fine in git.
|
||||||
val expected =
|
val expected =
|
||||||
mapOf(
|
mapOf(
|
||||||
"arm64-v8a" to Triple("aarch64-linux-android", 2, 0xB7),
|
"arm64-v8a" to Triple("aarch64-linux-android", 2, 0xB7),
|
||||||
@@ -405,48 +433,51 @@ val verifyArtiAbis =
|
|||||||
|
|
||||||
doLast {
|
doLast {
|
||||||
val bitness = mapOf(1 to "32-bit", 2 to "64-bit")
|
val bitness = mapOf(1 to "32-bit", 2 to "64-bit")
|
||||||
val problems = mutableListOf<Pair<String, String>>()
|
val problems = mutableListOf<Triple<String, String, String>>()
|
||||||
|
|
||||||
abis.forEach { abi ->
|
libs.keys.forEach { libName ->
|
||||||
val lib = File(jniLibs, "$abi/libarti_android.so")
|
abis.forEach { abi ->
|
||||||
val want = expected[abi]
|
val lib = File(jniLibs, "$abi/$libName")
|
||||||
val header = ByteArray(20)
|
val want = expected[abi]
|
||||||
val read = if (lib.isFile) lib.inputStream().use { it.read(header) } else -1
|
val header = ByteArray(20)
|
||||||
|
val read = if (lib.isFile) lib.inputStream().use { it.read(header) } else -1
|
||||||
|
|
||||||
val problem =
|
val problem =
|
||||||
when {
|
when {
|
||||||
!lib.isFile -> "no libarti_android.so"
|
!lib.isFile -> "no $libName"
|
||||||
want == null -> "no expected ELF identity recorded for this ABI"
|
want == null -> "no expected ELF identity recorded for this ABI"
|
||||||
read < header.size ||
|
read < header.size ||
|
||||||
header[0] != 0x7F.toByte() ||
|
header[0] != 0x7F.toByte() ||
|
||||||
header[1] != 'E'.code.toByte() ||
|
header[1] != 'E'.code.toByte() ||
|
||||||
header[2] != 'L'.code.toByte() ||
|
header[2] != 'L'.code.toByte() ||
|
||||||
header[3] != 'F'.code.toByte() -> "not an ELF file (truncated or corrupt)"
|
header[3] != 'F'.code.toByte() -> "not an ELF file (truncated or corrupt)"
|
||||||
header[4].toInt() != want.second ->
|
header[4].toInt() != want.second ->
|
||||||
"${bitness[header[4].toInt()] ?: "unknown-class"} ELF, expected ${bitness[want.second]}"
|
"${bitness[header[4].toInt()] ?: "unknown-class"} ELF, expected ${bitness[want.second]}"
|
||||||
else -> {
|
else -> {
|
||||||
val machine = (header[18].toInt() and 0xFF) or ((header[19].toInt() and 0xFF) shl 8)
|
val machine = (header[18].toInt() and 0xFF) or ((header[19].toInt() and 0xFF) shl 8)
|
||||||
if (machine != want.third) {
|
if (machine != want.third) {
|
||||||
"built for ELF machine 0x%02x, expected 0x%02x".format(machine, want.third)
|
"built for ELF machine 0x%02x, expected 0x%02x".format(machine, want.third)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (problem != null) problems += abi to problem
|
if (problem != null) problems += Triple(libName, abi, problem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (problems.isNotEmpty()) {
|
if (problems.isNotEmpty()) {
|
||||||
throw GradleException(
|
throw GradleException(
|
||||||
buildString {
|
buildString {
|
||||||
appendLine("libarti_android.so is missing or wrong for ${problems.size} ABI split(s):")
|
appendLine("Committed native libraries are missing or wrong for ${problems.size} (library, ABI split) pair(s):")
|
||||||
problems.forEach { (abi, problem) -> appendLine(" $abi: $problem") }
|
problems.forEach { (libName, abi, problem) -> appendLine(" $libName / $abi: $problem") }
|
||||||
appendLine("Those APK splits would install with Tor permanently unavailable.")
|
appendLine("Those APK splits would install with that library permanently unavailable.")
|
||||||
appendLine("Rebuild them (tools/arti-build/README.md):")
|
appendLine("Rebuild them:")
|
||||||
problems.forEach { (abi, _) ->
|
problems.forEach { (libName, abi, _) ->
|
||||||
val triple = expected[abi]?.first ?: "<add the Rust target for $abi>"
|
val triple = expected[abi]?.first ?: "<add the Rust target for $abi>"
|
||||||
appendLine(" ./tools/arti-build/build-arti.sh --target=$triple")
|
val rebuild = libs[libName]?.invoke(abi, triple) ?: "<no rebuild command recorded for $libName>"
|
||||||
|
appendLine(" $rebuild")
|
||||||
}
|
}
|
||||||
append("…or drop the ABI from `shippedAbis` in amethyst/build.gradle.kts.")
|
append("…or drop the ABI from `shippedAbis` in amethyst/build.gradle.kts.")
|
||||||
},
|
},
|
||||||
@@ -455,7 +486,9 @@ val verifyArtiAbis =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named("preBuild") { dependsOn(verifyArtiAbis) }
|
tasks.named("preBuild") {
|
||||||
|
dependsOn(verifyNativeAbis)
|
||||||
|
}
|
||||||
|
|
||||||
// androidx.appfunctions-compiler runs in a per-module mode by default,
|
// androidx.appfunctions-compiler runs in a per-module mode by default,
|
||||||
// emitting only the dispatcher Kotlin code. The aggregator that builds
|
// emitting only the dispatcher Kotlin code. The aggregator that builds
|
||||||
|
|||||||
@@ -196,8 +196,9 @@ The ABI list therefore lives in three places that must agree: `splits.abi` in
|
|||||||
`amethyst/build.gradle.kts`, `targets` in `rust-toolchain.toml`, and `TARGETS`
|
`amethyst/build.gradle.kts`, `targets` in `rust-toolchain.toml`, and `TARGETS`
|
||||||
in `build-arti.sh`. (`verify-reproducible.sh` has no copy of its own — it asks
|
in `build-arti.sh`. (`verify-reproducible.sh` has no copy of its own — it asks
|
||||||
`build-arti.sh --print-abis`, so it can never hash a different set than the one
|
`build-arti.sh --print-abis`, so it can never hash a different set than the one
|
||||||
it just rebuilt.) The `verifyArtiAbis` Gradle task, wired into `preBuild`, fails
|
it just rebuilt.) The `verifyNativeAbis` Gradle task, wired into `preBuild`, fails
|
||||||
the build when an ABI split has no `libarti_android.so` **or** has one that is
|
the build when an ABI split is missing any committed native library
|
||||||
|
(`libarti_android.so`, `libzxingcpp_android.so`) **or** has one that is
|
||||||
not an ELF of that architecture — a truncated file or arm64's library copied
|
not an ELF of that architecture — a truncated file or arm64's library copied
|
||||||
into `x86/` loads as nothing on device, exactly like a missing one, and unlike a
|
into `x86/` loads as nothing on device, exactly like a missing one, and unlike a
|
||||||
missing one it looks fine in `git status`.
|
missing one it looks fine in `git status`.
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
30.0.16248370
|
|
||||||
@@ -30,8 +30,10 @@ verify the binaries, bump the zxing-cpp version, or change the build flags.
|
|||||||
```
|
```
|
||||||
|
|
||||||
Prerequisites: `git`, `cmake`, `ninja`, and the exact NDK revision in
|
Prerequisites: `git`, `cmake`, `ninja`, and the exact NDK revision in
|
||||||
[`ANDROID_NDK_VERSION`](ANDROID_NDK_VERSION) — which is deliberately the same revision
|
[`tools/arti-build/ANDROID_NDK_VERSION`](../arti-build/ANDROID_NDK_VERSION). That is the
|
||||||
`tools/arti-build` pins, so one NDK install serves both native builds.
|
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
|
## Reproducible builds
|
||||||
|
|
||||||
@@ -39,7 +41,7 @@ Five things have to be fixed, and each is:
|
|||||||
|
|
||||||
| Source of non-determinism | Pinned by |
|
| Source of non-determinism | Pinned by |
|
||||||
|---|---|
|
|---|---|
|
||||||
| compiler + linker version | [`ANDROID_NDK_VERSION`](ANDROID_NDK_VERSION); `build-zxingcpp.sh` refuses any other revision |
|
| 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 |
|
| 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) |
|
| 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 |
|
| timestamps | `SOURCE_DATE_EPOCH`, derived from the pinned tag's commit rather than from build time; `__DATE__`/`__TIME__` redacted |
|
||||||
|
|||||||
@@ -13,15 +13,22 @@
|
|||||||
# ./build-zxingcpp.sh --out DIR # write .so somewhere else (verify uses this)
|
# ./build-zxingcpp.sh --out DIR # write .so somewhere else (verify uses this)
|
||||||
#
|
#
|
||||||
# Prerequisites: git, cmake, ninja, and the exact NDK revision in
|
# Prerequisites: git, cmake, ninja, and the exact NDK revision in
|
||||||
# ANDROID_NDK_VERSION. Any other revision is refused — it would change the
|
# tools/arti-build/ANDROID_NDK_VERSION. Any other revision is refused — it
|
||||||
# output bytes, which is the whole point.
|
# would change the output bytes, which is the whole point.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
|
||||||
ZXING_VERSION="$(tr -d '[:space:]' < "$SCRIPT_DIR/ZXING_CPP_VERSION")"
|
ZXING_VERSION="$(tr -d '[:space:]' < "$SCRIPT_DIR/ZXING_CPP_VERSION")"
|
||||||
NDK_VERSION="$(tr -d '[:space:]' < "$SCRIPT_DIR/ANDROID_NDK_VERSION")"
|
# One pin for the whole repo, deliberately not a copy of our own. :amethyst
|
||||||
|
# already reads this same file for `ndkVersion` (the NDK that strips whatever
|
||||||
|
# lands in src/main/jniLibs), so a second copy here could only ever be a way
|
||||||
|
# to disagree with the toolchain that packages the .so we produce — the exact
|
||||||
|
# byte-level drift the pin exists to prevent. Bumping it rebuilds both
|
||||||
|
# libarti_android.so and libzxingcpp_android.so, which is correct: they must
|
||||||
|
# be built and stripped by one NDK.
|
||||||
|
NDK_VERSION="$(tr -d '[:space:]' < "$PROJECT_ROOT/tools/arti-build/ANDROID_NDK_VERSION")"
|
||||||
|
|
||||||
# Canonical build path. Codegen and link ordering can key on the real build
|
# Canonical build path. Codegen and link ordering can key on the real build
|
||||||
# directory even with path remapping in place, so everyone builds here or
|
# directory even with path remapping in place, so everyone builds here or
|
||||||
@@ -33,9 +40,10 @@ OUTPUT_DIR="$PROJECT_ROOT/amethyst/src/main/jniLibs"
|
|||||||
LIB_NAME="libzxingcpp_android.so"
|
LIB_NAME="libzxingcpp_android.so"
|
||||||
MIN_SDK_VERSION=26
|
MIN_SDK_VERSION=26
|
||||||
|
|
||||||
# Every ABI the app splits on. Unlike Tor — an optional feature that ships on
|
# Every ABI the app splits on. A QR scanner that does not load is a broken core
|
||||||
# two ABIs — a QR scanner that does not load is a broken core feature, and the
|
# feature — the decoder this replaced was pure Java and worked everywhere — so
|
||||||
# decoder this replaced was pure Java and worked everywhere.
|
# every split gets one, and :amethyst's verifyNativeAbis fails the build if one
|
||||||
|
# is missing or built for the wrong architecture.
|
||||||
ABIS=(arm64-v8a armeabi-v7a x86 x86_64)
|
ABIS=(arm64-v8a armeabi-v7a x86 x86_64)
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
|
|||||||
Reference in New Issue
Block a user