mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
Follow-up to the toolchain update, from an audit of that diff. Build script: - The NDK pin rejected machines that have the pinned revision installed. An exported ANDROID_NDK_HOME or ANDROID_NDK_ROOT short-circuited the search and then failed the revision check, and GitHub runners export both at their own bundled NDK. Every candidate is now checked against its own source.properties and a mismatch moves on, so the build fails only when the pinned revision is genuinely absent, listing what it found instead. - verify_jni_symbols printed missing exports and exited 0, so a library that would throw UnsatisfiedLinkError on every call could ship. It now fails the build, and checks only the ABIs this run built. - Both post-build checks now use the pinned NDK's own llvm-readelf and llvm-nm. The stamp check silently skipped on macOS, which has no readelf, and Apple's nm cannot read ELF at all, so the symbol check would have reported every symbol missing there. - The stamp check read its note through `readelf | grep -q`, the same SIGPIPE-plus-pipefail shape this branch removed from the symbol check. - $HOME is expanded with a default, so `set -u` no longer aborts before the "NDK not found" message in an environment without HOME. verify-reproducible.sh hashed every .so under jniLibs, so --release, which rebuilds arm64 only, hashed the untouched x86_64 library identically in both runs and reported the whole tree reproducible and matching the commit. It now hashes and diffs only the ABIs the run builds, and prints which those are. lib.rs: - initialize() signalled "already initialized" out of the JNI closure as an empty string, re-tested after it. A destroy() landing in between would let the empty string through as the data directory, which resolves to relative state/ and cache/ paths against the process working directory. The check now reads the whole Option outside the closure and no sentinel exists. - Corrected the comments claiming the error policy keeps a panic from crossing extern "C". It does not: the policy's panic arm runs through catch_unwind, which catches nothing under this crate's panic = "abort" profile. The Err arm, which is what the code relies on, is unaffected. README: the troubleshooting section still told readers to install cargo-ndk unpinned and to export ANDROID_NDK_HOME at an arbitrary revision, which was the exact way to trip the old gate. Verified: two clean builds byte-for-byte identical, both ABIs stamped r30, JNI exports present, 16 KiB alignment kept. JVM tier-3 smoke test green, and a scratch harness drove getVersion, setLogCallback, initialize, a second initialize on a live client (the reuse path the sentinel used to carry) and destroy over real JNI. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011cSuXeu4bUTNRAUCZJcLLW
88 lines
3.3 KiB
Bash
Executable File
88 lines
3.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Verify that libarti_android.so builds reproducibly.
|
|
#
|
|
# Builds the Arti native library twice from a clean state and confirms the two
|
|
# outputs are byte-for-byte identical. Both builds compile in the canonical path
|
|
# (/tmp/amethyst-arti-build), so a match here means any checkout — ours,
|
|
# F-Droid's, an auditor's — produces the same bytes. See README.md →
|
|
# "Reproducible builds".
|
|
#
|
|
# Usage:
|
|
# ./verify-reproducible.sh # both ABIs (arm64-v8a + x86_64)
|
|
# ./verify-reproducible.sh --release # arm64-v8a only (faster)
|
|
#
|
|
# Prerequisites are the same as build-arti.sh (rustup, cargo-ndk, and the exact
|
|
# Android NDK revision pinned in ANDROID_NDK_VERSION — a different revision is
|
|
# refused, because it would change the output bytes).
|
|
# Exit 0 = reproducible, exit 1 = builds differ.
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
JNILIBS="$PROJECT_ROOT/amethyst/src/main/jniLibs"
|
|
PASSTHRU=("$@")
|
|
|
|
# Portable sha256 (coreutils sha256sum on Linux, shasum on macOS).
|
|
sha256() {
|
|
if command -v sha256sum >/dev/null 2>&1; then sha256sum "$@"; else shasum -a 256 "$@"; fi
|
|
}
|
|
|
|
# Only the ABIs this run actually rebuilds. Hashing everything under jniLibs/
|
|
# (what `find` used to do) made `--release` look like it had verified the
|
|
# x86_64 library: that build never touches it, so the untouched file hashed
|
|
# identically in both runs and the script reported the whole tree reproducible
|
|
# and matching the commit.
|
|
ABIS="arm64-v8a x86_64"
|
|
for arg in ${PASSTHRU[@]+"${PASSTHRU[@]}"}; do
|
|
[ "$arg" = "--release" ] && ABIS="arm64-v8a"
|
|
done
|
|
|
|
# sha256 of each built .so, keyed by ABI dir (relative paths → stable keys).
|
|
hashes() {
|
|
( cd "$JNILIBS" && for abi in $ABIS; do
|
|
[ -f "$abi/libarti_android.so" ] && sha256 "$abi/libarti_android.so"
|
|
done )
|
|
}
|
|
|
|
echo "### Reproducibility check for libarti_android.so"
|
|
echo "### ABIs: $ABIS"
|
|
echo "### Canonical build path: ${ARTI_REPRO_DIR:-/tmp/amethyst-arti-build}"
|
|
echo
|
|
|
|
echo "### Build 1 of 2 (clean)…"
|
|
"$SCRIPT_DIR/build-arti.sh" --clean ${PASSTHRU[@]+"${PASSTHRU[@]}"}
|
|
H1="$(hashes)"
|
|
echo "--- build 1 hashes ---"; echo "$H1"; echo
|
|
|
|
echo "### Build 2 of 2 (clean)…"
|
|
"$SCRIPT_DIR/build-arti.sh" --clean ${PASSTHRU[@]+"${PASSTHRU[@]}"}
|
|
H2="$(hashes)"
|
|
echo "--- build 2 hashes ---"; echo "$H2"; echo
|
|
|
|
if [ "$H1" = "$H2" ]; then
|
|
echo "✅ REPRODUCIBLE — both clean builds produced identical .so bytes."
|
|
else
|
|
echo "❌ NOT REPRODUCIBLE — the two builds differ:"
|
|
diff <(echo "$H1") <(echo "$H2") || true
|
|
exit 1
|
|
fi
|
|
|
|
# Informational: is the binary committed in git already the reproducible one?
|
|
echo
|
|
echo "### vs. the committed binaries:"
|
|
BUILT_PATHS=""
|
|
for abi in $ABIS; do
|
|
BUILT_PATHS="$BUILT_PATHS amethyst/src/main/jniLibs/$abi/libarti_android.so"
|
|
done
|
|
|
|
# shellcheck disable=SC2086 # BUILT_PATHS is a deliberate multi-path list
|
|
if git -C "$PROJECT_ROOT" diff --quiet -- $BUILT_PATHS; then
|
|
echo "✓ The reproducible build matches what's committed — the shipped .so is verifiable as-is."
|
|
else
|
|
echo "⚠ The reproducible build differs from the committed .so (e.g. the committed one"
|
|
echo " predates this toolchain). Commit the rebuilt binaries so the shipped artifact"
|
|
echo " is itself a reproducible build:"
|
|
echo " git -C \"$PROJECT_ROOT\" add$BUILT_PATHS && git commit"
|
|
fi
|