mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-09-14 00:55:08 +00:00
Merge pull request #4120 from vitorpamplona/claude/sweet-fermat-10uvw5
Update Arti to 2.6.0 and jni to 0.22, pin and enforce the NDK revision
This commit is contained in:
+3
-2
@@ -105,8 +105,9 @@ and each has its own guide:
|
|||||||
> 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
|
Both tools have their own prerequisites (`fonttools`/`brotli` for the font; a
|
||||||
Rust toolchain + Android NDK 25+ for Arti) documented in their READMEs — they
|
Rust toolchain + the exact Android NDK revision pinned in
|
||||||
are **not** required to build Amethyst from the committed sources.
|
`tools/arti-build/ANDROID_NDK_VERSION` for Arti) documented in their READMEs —
|
||||||
|
they are **not** required to build Amethyst from the committed sources.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
30.0.16248370
|
||||||
@@ -1 +1 @@
|
|||||||
arti-v2.3.0
|
arti-v2.6.0
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
4.1.2
|
||||||
Generated
+821
-597
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "arti-android"
|
name = "arti-android"
|
||||||
version = "2.3.0"
|
version = "2.6.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
@@ -9,20 +9,21 @@ crate-type = ["cdylib"]
|
|||||||
[workspace]
|
[workspace]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arti-client = { version = "0.42", default-features = false, features = [
|
arti-client = { version = "0.46", default-features = false, features = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"rustls",
|
"rustls",
|
||||||
"compression",
|
"compression",
|
||||||
"onion-service-client",
|
"onion-service-client",
|
||||||
"static-sqlite",
|
"static-sqlite",
|
||||||
] }
|
] }
|
||||||
tor-rtcompat = { version = "0.42", default-features = false, features = ["tokio", "rustls"] }
|
tor-rtcompat = { version = "0.46", default-features = false, features = ["tokio", "rustls"] }
|
||||||
# Direct dep on rustls so we can install the `ring` crypto provider ourselves —
|
# Direct dep on rustls so we can install the `ring` crypto provider ourselves —
|
||||||
# arti-v2.3.0's tor-rtcompat no longer installs one implicitly. `ring` matches
|
# since arti-v2.3.0 tor-rtcompat no longer installs one implicitly. `ring`
|
||||||
# what arti-v2.2.0 effectively used and avoids the Android build pain of
|
# matches what arti-v2.2.0 effectively used and avoids the Android build pain
|
||||||
# aws-lc-rs (which became Arti's default in 2.3.0).
|
# of aws-lc-rs (Arti's default since 2.3.0), which `default-features = false`
|
||||||
|
# keeps out of the build entirely.
|
||||||
rustls = { version = "0.23", default-features = false, features = ["ring", "std"] }
|
rustls = { version = "0.23", default-features = false, features = ["ring", "std"] }
|
||||||
jni = "0.21"
|
jni = "0.22"
|
||||||
tokio = { version = "1", features = ["rt-multi-thread", "net", "io-util", "time", "macros"] }
|
tokio = { version = "1", features = ["rt-multi-thread", "net", "io-util", "time", "macros"] }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
|
||||||
|
|||||||
+70
-15
@@ -9,9 +9,9 @@ JNI wrapper built directly from Arti source.
|
|||||||
| | Guardian Project AAR | Custom build |
|
| | Guardian Project AAR | Custom build |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **Size** | ~140MB | ~11MB |
|
| **Size** | ~140MB | ~11MB |
|
||||||
| **16KB pages** | No | Yes (NDK 25+) |
|
| **16KB pages** | No | Yes (rustc aligns Android targets to 16 KiB) |
|
||||||
| **Stop/restart** | Broken (state file lock) | Works (TorClient persists, only SOCKS proxy stops) |
|
| **Stop/restart** | Broken (state file lock) | Works (TorClient persists, only SOCKS proxy stops) |
|
||||||
| **Version** | Behind | Pinned to latest (currently 1.9.0) |
|
| **Version** | Behind | Pinned to latest (see [`ARTI_VERSION`](ARTI_VERSION)) |
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
@@ -22,15 +22,34 @@ rebuild if you want to verify binaries, update the Arti version, or modify the J
|
|||||||
|
|
||||||
The shipped `.so` is **built to be reproducible** so anyone — F-Droid, Zapstore,
|
The shipped `.so` is **built to be reproducible** so anyone — F-Droid, Zapstore,
|
||||||
or an independent auditor — can rebuild it from this tag and confirm the
|
or an independent auditor — can rebuild it from this tag and confirm the
|
||||||
committed binary wasn't tampered with. **Four** things have to be fixed:
|
committed binary wasn't tampered with. **Five** things have to be fixed:
|
||||||
|
|
||||||
| Source of non-determinism | Pinned by |
|
| Source of non-determinism | Pinned by |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `rustc` / cargo version | [`rust-toolchain.toml`](rust-toolchain.toml) (rustup auto-installs it) |
|
| `rustc` / cargo version | [`rust-toolchain.toml`](rust-toolchain.toml) (rustup auto-installs it) |
|
||||||
|
| **Android NDK revision** | [`ANDROID_NDK_VERSION`](ANDROID_NDK_VERSION); `build-arti.sh` refuses to build with any other revision |
|
||||||
| transitive dependency versions | committed [`Cargo.lock`](Cargo.lock); builds run `cargo --locked` |
|
| transitive dependency versions | committed [`Cargo.lock`](Cargo.lock); builds run `cargo --locked` |
|
||||||
| absolute paths *embedded* in the binary | `--remap-path-prefix` in [`repro-env.sh`](repro-env.sh) |
|
| absolute paths *embedded* in the binary | `--remap-path-prefix` in [`repro-env.sh`](repro-env.sh) |
|
||||||
| codegen/link **ordering** keyed on the real build path | **canonical build path** (`build-arti.sh` builds in `/tmp/amethyst-arti-build`) |
|
| codegen/link **ordering** keyed on the real build path | **canonical build path** (`build-arti.sh` builds in `/tmp/amethyst-arti-build`) |
|
||||||
|
|
||||||
|
> **Why the NDK is pinned.** It is not just an SDK detail: the NDK supplies the
|
||||||
|
> clang that compiles Arti's C dependencies (`ring`, `zstd-sys`,
|
||||||
|
> `libsqlite3-sys`) and the `lld` that links the final `cdylib`, both of which
|
||||||
|
> stamp themselves into the binary's `.comment` section next to `rustc`'s own
|
||||||
|
> version. Swapping the NDK changes the bytes exactly like swapping `rustc`
|
||||||
|
> would. Before this was pinned the build picked the first directory matching
|
||||||
|
> `~/Android/Sdk/ndk/*/`, so the committed libraries were produced by r25b while
|
||||||
|
> this file told everyone to install r27 — two verifiers could both follow the
|
||||||
|
> README and get different, equally "correct" results. `build-arti.sh` now
|
||||||
|
> reads each candidate's `source.properties` and keeps looking until it finds
|
||||||
|
> the pinned revision, then re-checks the `.note.android.ident` stamp of every
|
||||||
|
> `.so` it produced.
|
||||||
|
>
|
||||||
|
> [`CARGO_NDK_VERSION`](CARGO_NDK_VERSION) records the `cargo-ndk` release the
|
||||||
|
> 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.
|
||||||
|
|
||||||
`repro-env.sh` (sourced by both build scripts) also sets `CARGO_INCREMENTAL=0`
|
`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
|
and a fixed `SOURCE_DATE_EPOCH` derived from the Arti tag. The size-optimized
|
||||||
release profile in `Cargo.toml` (`lto`, `codegen-units = 1`, `strip`,
|
release profile in `Cargo.toml` (`lto`, `codegen-units = 1`, `strip`,
|
||||||
@@ -74,20 +93,27 @@ repo is checked out.
|
|||||||
rustup target add aarch64-linux-android x86_64-linux-android
|
rustup target add aarch64-linux-android x86_64-linux-android
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **cargo-ndk**
|
3. **cargo-ndk** — the release the pinned output was verified with:
|
||||||
```bash
|
```bash
|
||||||
cargo install cargo-ndk
|
cargo install cargo-ndk --version "$(cat CARGO_NDK_VERSION)" --locked
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Android NDK 25+** (required for 16KB page size support)
|
4. **Android NDK** — the exact revision in [`ANDROID_NDK_VERSION`](ANDROID_NDK_VERSION)
|
||||||
|
(currently **30.0.16248370**, r30). Any other revision is refused: it would
|
||||||
|
produce a `.so` that does not match the committed one.
|
||||||
```bash
|
```bash
|
||||||
# Via Android Studio: SDK Manager → SDK Tools → NDK (Side by side)
|
# Via Android Studio: SDK Manager → SDK Tools → NDK (Side by side)
|
||||||
# Or via command line:
|
# Or via command line:
|
||||||
sdkmanager "ndk;27.0.12077973"
|
sdkmanager "ndk;$(cat ANDROID_NDK_VERSION)"
|
||||||
|
|
||||||
# Set environment variable
|
|
||||||
export ANDROID_NDK_HOME="$HOME/Android/Sdk/ndk/27.0.12077973"
|
|
||||||
```
|
```
|
||||||
|
`build-arti.sh` finds it automatically under `$ANDROID_HOME/ndk/`,
|
||||||
|
`$ANDROID_SDK_ROOT/ndk/`, `~/Android/Sdk/ndk/`, `~/Library/Android/sdk/ndk/`
|
||||||
|
or `/usr/local/lib/android/sdk/ndk/`. `ANDROID_NDK_HOME` and
|
||||||
|
`ANDROID_NDK_ROOT` are tried first when set, but they are only hints: every
|
||||||
|
candidate is checked against its own `source.properties`, and one at the
|
||||||
|
wrong revision is reported and skipped rather than failing the build. CI
|
||||||
|
images (GitHub runners among them) export both at a bundled NDK that is not
|
||||||
|
ours.
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
@@ -130,14 +156,38 @@ Google Play requires 16KB page-aligned native libraries. Verify with:
|
|||||||
readelf -l amethyst/src/main/jniLibs/arm64-v8a/libarti_android.so | grep LOAD
|
readelf -l amethyst/src/main/jniLibs/arm64-v8a/libarti_android.so | grep LOAD
|
||||||
```
|
```
|
||||||
|
|
||||||
The first LOAD segment alignment should be `0x4000` (16384 bytes).
|
The first LOAD segment alignment should be `0x4000` (16384 bytes). This comes
|
||||||
|
from rustc's Android target spec (`max-page-size=16384`), not from the NDK, so
|
||||||
|
it holds for every NDK revision we could build with.
|
||||||
|
|
||||||
|
## Checking which toolchain built a `.so`
|
||||||
|
|
||||||
|
The shipped binaries say so themselves — useful when a rebuild does not match, or
|
||||||
|
when auditing a `.so` you did not build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# NDK release name + build number (the last component of the pinned revision)
|
||||||
|
readelf -p .note.android.ident amethyst/src/main/jniLibs/arm64-v8a/libarti_android.so
|
||||||
|
|
||||||
|
# clang / lld (from the NDK) and rustc versions
|
||||||
|
readelf -p .comment amethyst/src/main/jniLibs/arm64-v8a/libarti_android.so
|
||||||
|
```
|
||||||
|
|
||||||
|
For the pinned toolchain the first command prints `r30` and build `16248370`.
|
||||||
|
The second prints the `rustc` version from `rust-toolchain.toml`, the NDK's
|
||||||
|
`clang` and `LLD`, and a second, different clang string that comes from the
|
||||||
|
prebuilt runtime objects the NDK links in — two clang lines there is normal.
|
||||||
|
`build-arti.sh` runs the first check itself after every build, using the NDK's
|
||||||
|
own `llvm-readelf` so it works the same on macOS.
|
||||||
|
|
||||||
## Directory structure
|
## Directory structure
|
||||||
|
|
||||||
```
|
```
|
||||||
tools/arti-build/
|
tools/arti-build/
|
||||||
├── README.md # This file
|
├── README.md # This file
|
||||||
├── ARTI_VERSION # Pinned Arti git tag (e.g., arti-v1.9.0)
|
├── ARTI_VERSION # Pinned Arti git tag (e.g., arti-v2.6.0)
|
||||||
|
├── ANDROID_NDK_VERSION # Pinned NDK revision — enforced by build-arti.sh (reproducibility)
|
||||||
|
├── CARGO_NDK_VERSION # cargo-ndk release the pinned output was verified with
|
||||||
├── rust-toolchain.toml # Pinned rustc version + Android targets (reproducibility)
|
├── rust-toolchain.toml # Pinned rustc version + Android targets (reproducibility)
|
||||||
├── Cargo.toml # Rust dependencies and build profile
|
├── Cargo.toml # Rust dependencies and build profile
|
||||||
├── Cargo.lock # Pinned transitive dependency versions (reproducibility)
|
├── Cargo.lock # Pinned transitive dependency versions (reproducibility)
|
||||||
@@ -243,13 +293,18 @@ panic = "abort" # No unwinding (smaller binary)
|
|||||||
|
|
||||||
### `cargo-ndk` not found
|
### `cargo-ndk` not found
|
||||||
```bash
|
```bash
|
||||||
cargo install cargo-ndk
|
cargo install cargo-ndk --version "$(cat CARGO_NDK_VERSION)" --locked
|
||||||
```
|
```
|
||||||
|
|
||||||
### NDK not found
|
### NDK not found, or "wrong revision"
|
||||||
|
Install the pinned revision — the build refuses any other, and the error lists
|
||||||
|
every directory it looked at and what it found there:
|
||||||
```bash
|
```bash
|
||||||
export ANDROID_NDK_HOME="$HOME/Android/Sdk/ndk/<version>"
|
sdkmanager "ndk;$(cat ANDROID_NDK_VERSION)"
|
||||||
```
|
```
|
||||||
|
Point `ANDROID_NDK_HOME` at it only if it lives outside the standard SDK
|
||||||
|
layouts; an `ANDROID_NDK_HOME` left over from another project is skipped, not
|
||||||
|
fatal.
|
||||||
|
|
||||||
### Rust targets not installed
|
### Rust targets not installed
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
+168
-26
@@ -6,7 +6,8 @@
|
|||||||
# - Rust toolchain: rustup, cargo
|
# - Rust toolchain: rustup, cargo
|
||||||
# - Android targets: rustup target add aarch64-linux-android x86_64-linux-android
|
# - Android targets: rustup target add aarch64-linux-android x86_64-linux-android
|
||||||
# - cargo-ndk: cargo install cargo-ndk
|
# - cargo-ndk: cargo install cargo-ndk
|
||||||
# - Android NDK 25+ (for 16KB page size support)
|
# - Android NDK: the exact revision pinned in ANDROID_NDK_VERSION
|
||||||
|
# (sdkmanager "ndk;<revision>") — see README.md -> "Reproducible builds"
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./build-arti.sh # Build for all targets (arm64 + x86_64)
|
# ./build-arti.sh # Build for all targets (arm64 + x86_64)
|
||||||
@@ -26,6 +27,22 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
ARTI_VERSION=$(cat "$SCRIPT_DIR/ARTI_VERSION" | tr -d '[:space:]')
|
ARTI_VERSION=$(cat "$SCRIPT_DIR/ARTI_VERSION" | tr -d '[:space:]')
|
||||||
|
|
||||||
|
# Reproducibility: the NDK ships the clang that compiles Arti's C dependencies
|
||||||
|
# (ring, zstd-sys, libsqlite3-sys) and the lld that links the whole cdylib, so
|
||||||
|
# its revision is baked into the output bytes exactly like rustc's is — both
|
||||||
|
# land in the .comment section of the shipped .so. Pin it here and refuse to
|
||||||
|
# build with anything else; the old glob over ~/Android/Sdk/ndk/*/ silently
|
||||||
|
# picked up whatever happened to be installed first.
|
||||||
|
NDK_VERSION=$(cat "$SCRIPT_DIR/ANDROID_NDK_VERSION" | tr -d '[:space:]')
|
||||||
|
# The NDK build number (last component of the revision) is what the linker
|
||||||
|
# stamps into .note.android.ident, so it is how we verify the output afterwards.
|
||||||
|
NDK_BUILD_NUMBER="${NDK_VERSION##*.}"
|
||||||
|
# cargo-ndk only wraps the NDK (it sets CC/AR/linker and the --platform flags),
|
||||||
|
# but those flags reach the linker, so record the version we verified with and
|
||||||
|
# warn when it differs. Not a hard error: unlike the NDK itself, it has no
|
||||||
|
# proven effect on the bytes.
|
||||||
|
CARGO_NDK_VERSION=$(cat "$SCRIPT_DIR/CARGO_NDK_VERSION" | tr -d '[:space:]')
|
||||||
|
|
||||||
# Reproducibility: rustc bakes the *real* (un-remapped) absolute paths of the
|
# Reproducibility: rustc bakes the *real* (un-remapped) absolute paths of the
|
||||||
# build artifacts into its codegen/link ORDERING, so --remap-path-prefix alone
|
# build artifacts into its codegen/link ORDERING, so --remap-path-prefix alone
|
||||||
# is not enough — the .so only reproduces byte-for-byte when the compile happens
|
# is not enough — the .so only reproduces byte-for-byte when the compile happens
|
||||||
@@ -68,33 +85,89 @@ print_info() { echo -e "${YELLOW}→ $1${NC}"; }
|
|||||||
# Prerequisites
|
# Prerequisites
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
# Pkg.Revision of an NDK install, or empty if the directory is not one.
|
||||||
|
ndk_revision() {
|
||||||
|
sed -n 's/^Pkg\.Revision *= *//p' "$1/source.properties" 2>/dev/null | tr -d '[:space:]' || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Path to an ELF tool, preferring the pinned NDK's own llvm-* copy. The NDK
|
||||||
|
# ships them on every platform, which keeps the post-build checks working on
|
||||||
|
# macOS: there is no readelf in the Xcode command line tools, and Apple's nm
|
||||||
|
# cannot read ELF at all, so the checks would otherwise skip or report every
|
||||||
|
# symbol missing on exactly the machines most likely to have the wrong NDK.
|
||||||
|
ndk_tool() {
|
||||||
|
local name="$1" candidate
|
||||||
|
for candidate in "${ANDROID_NDK_HOME:-}"/toolchains/llvm/prebuilt/*/bin/"llvm-$name"; do
|
||||||
|
if [ -x "$candidate" ]; then
|
||||||
|
echo "$candidate"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
command -v "$name" 2>/dev/null && return 0
|
||||||
|
command -v "g$name" 2>/dev/null && return 0
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
check_prerequisites() {
|
check_prerequisites() {
|
||||||
print_header "Checking prerequisites"
|
print_header "Checking prerequisites"
|
||||||
|
|
||||||
command -v git >/dev/null 2>&1 || { print_error "git not found"; exit 1; }
|
command -v git >/dev/null 2>&1 || { print_error "git not found"; exit 1; }
|
||||||
command -v rustup >/dev/null 2>&1 || { print_error "rustup not found"; exit 1; }
|
command -v rustup >/dev/null 2>&1 || { print_error "rustup not found"; exit 1; }
|
||||||
command -v cargo >/dev/null 2>&1 || { print_error "cargo not found"; exit 1; }
|
command -v cargo >/dev/null 2>&1 || { print_error "cargo not found"; exit 1; }
|
||||||
command -v cargo-ndk >/dev/null 2>&1 || { print_error "cargo-ndk not found. Install: cargo install cargo-ndk"; exit 1; }
|
command -v cargo-ndk >/dev/null 2>&1 || { print_error "cargo-ndk not found. Install: cargo install cargo-ndk --version $CARGO_NDK_VERSION --locked"; exit 1; }
|
||||||
|
|
||||||
if [ -z "${ANDROID_NDK_HOME:-}" ]; then
|
local found_cargo_ndk
|
||||||
# Try common locations
|
found_cargo_ndk="$(cargo ndk --version 2>/dev/null | awk '{print $2}' || true)"
|
||||||
for candidate in \
|
if [ "$found_cargo_ndk" != "$CARGO_NDK_VERSION" ]; then
|
||||||
"$HOME/Android/Sdk/ndk/"*/ \
|
print_info "cargo-ndk ${found_cargo_ndk:-unknown} != pinned $CARGO_NDK_VERSION — if the"
|
||||||
"$HOME/Library/Android/sdk/ndk/"*/ \
|
print_info " output does not match the committed .so, try: cargo install cargo-ndk --version $CARGO_NDK_VERSION --locked"
|
||||||
"/usr/local/lib/android/sdk/ndk/"*/; do
|
else
|
||||||
if [ -d "$candidate" ]; then
|
print_success "cargo-ndk: $CARGO_NDK_VERSION"
|
||||||
export ANDROID_NDK_HOME="${candidate%/}"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "${ANDROID_NDK_HOME:-}" ]; then
|
# Find the pinned revision wherever it lives, checking each candidate's own
|
||||||
print_error "ANDROID_NDK_HOME not set and NDK not found in common locations"
|
# source.properties and moving on when it does not match. An exported
|
||||||
|
# ANDROID_NDK_HOME / ANDROID_NDK_ROOT is only a hint: CI images (GitHub
|
||||||
|
# runners export both) and IDE installs routinely point them at a bundled
|
||||||
|
# NDK that is not ours, and failing outright there would reject a machine
|
||||||
|
# that has the pinned revision installed right next to it. No wildcard
|
||||||
|
# anywhere: picking "some NDK" is what let the committed binaries be built
|
||||||
|
# with r25b while the docs asked for r27.
|
||||||
|
local candidate revision found_ndk="" rejected=""
|
||||||
|
for candidate in \
|
||||||
|
"${ANDROID_NDK_HOME:-}" \
|
||||||
|
"${ANDROID_NDK_ROOT:-}" \
|
||||||
|
"${ANDROID_HOME:-}/ndk/$NDK_VERSION" \
|
||||||
|
"${ANDROID_SDK_ROOT:-}/ndk/$NDK_VERSION" \
|
||||||
|
"${HOME:-}/Android/Sdk/ndk/$NDK_VERSION" \
|
||||||
|
"${HOME:-}/Library/Android/sdk/ndk/$NDK_VERSION" \
|
||||||
|
"/usr/local/lib/android/sdk/ndk/$NDK_VERSION"; do
|
||||||
|
[ -n "$candidate" ] || continue
|
||||||
|
[ -d "$candidate" ] || continue
|
||||||
|
|
||||||
|
revision="$(ndk_revision "$candidate")"
|
||||||
|
if [ "$revision" = "$NDK_VERSION" ]; then
|
||||||
|
found_ndk="${candidate%/}"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
rejected="${rejected} ${candidate%/} is ${revision:-not an NDK}"$'\n'
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$found_ndk" ]; then
|
||||||
|
print_error "Android NDK $NDK_VERSION not found"
|
||||||
|
echo " It is pinned because another revision produces a .so that does not"
|
||||||
|
echo " match the committed one (tools/arti-build/ANDROID_NDK_VERSION)."
|
||||||
|
if [ -n "$rejected" ]; then
|
||||||
|
echo " Looked at, wrong revision:"
|
||||||
|
printf '%s' "$rejected"
|
||||||
|
fi
|
||||||
|
echo " Install it: sdkmanager \"ndk;$NDK_VERSION\""
|
||||||
|
echo " Or point ANDROID_NDK_HOME at an existing $NDK_VERSION install."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
print_success "NDK: $ANDROID_NDK_HOME"
|
export ANDROID_NDK_HOME="$found_ndk"
|
||||||
|
print_success "NDK: $ANDROID_NDK_HOME ($NDK_VERSION)"
|
||||||
|
|
||||||
for target in "${TARGETS[@]}"; do
|
for target in "${TARGETS[@]}"; do
|
||||||
if ! rustup target list --installed | grep -q "$target"; then
|
if ! rustup target list --installed | grep -q "$target"; then
|
||||||
@@ -180,17 +253,22 @@ PATCH
|
|||||||
# Build
|
# Build
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
# Android ABI directory (as laid out under jniLibs/) for a Rust target triple.
|
||||||
|
abi_dir_for() {
|
||||||
|
case "$1" in
|
||||||
|
aarch64-linux-android) echo "arm64-v8a" ;;
|
||||||
|
x86_64-linux-android) echo "x86_64" ;;
|
||||||
|
armv7-linux-androideabi) echo "armeabi-v7a" ;;
|
||||||
|
i686-linux-android) echo "x86" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
build_for_target() {
|
build_for_target() {
|
||||||
local target="$1"
|
local target="$1"
|
||||||
print_header "Building for $target"
|
print_header "Building for $target"
|
||||||
|
|
||||||
local arch_dir
|
local arch_dir
|
||||||
case "$target" in
|
arch_dir="$(abi_dir_for "$target")"
|
||||||
aarch64-linux-android) arch_dir="arm64-v8a" ;;
|
|
||||||
x86_64-linux-android) arch_dir="x86_64" ;;
|
|
||||||
armv7-linux-androideabi) arch_dir="armeabi-v7a" ;;
|
|
||||||
i686-linux-android) arch_dir="x86" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
local out_dir="$OUTPUT_DIR/$arch_dir"
|
local out_dir="$OUTPUT_DIR/$arch_dir"
|
||||||
mkdir -p "$out_dir"
|
mkdir -p "$out_dir"
|
||||||
@@ -229,15 +307,32 @@ verify_jni_symbols() {
|
|||||||
"Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_destroy"
|
"Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_destroy"
|
||||||
)
|
)
|
||||||
|
|
||||||
for arch_dir in "$OUTPUT_DIR"/*/; do
|
local nm_bin
|
||||||
local lib="$arch_dir$LIB_NAME"
|
nm_bin="$(ndk_tool nm || true)"
|
||||||
|
if [ -z "$nm_bin" ]; then
|
||||||
|
print_error "no nm found (looked in the NDK and on PATH) — cannot verify the JNI exports"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local failed=0
|
||||||
|
for target in "${TARGETS[@]}"; do
|
||||||
|
local arch
|
||||||
|
arch="$(abi_dir_for "$target")"
|
||||||
|
local lib="$OUTPUT_DIR/$arch/$LIB_NAME"
|
||||||
[ -f "$lib" ] || continue
|
[ -f "$lib" ] || continue
|
||||||
|
|
||||||
local arch=$(basename "$arch_dir")
|
|
||||||
local missing=0
|
local missing=0
|
||||||
|
|
||||||
|
# Read the dynamic symbol table once, into a variable. Piping nm into
|
||||||
|
# `grep -q` per symbol looks equivalent but is not: grep exits on the
|
||||||
|
# first match, nm dies of SIGPIPE (141), and `set -o pipefail` then
|
||||||
|
# reports the pipeline as failed — so every symbol that IS exported gets
|
||||||
|
# reported as missing. (Reproducible on any build, old or new.)
|
||||||
|
local syms
|
||||||
|
syms="$("$nm_bin" -D "$lib" 2>/dev/null || true)"
|
||||||
|
|
||||||
for sym in "${expected_symbols[@]}"; do
|
for sym in "${expected_symbols[@]}"; do
|
||||||
if ! nm -D "$lib" 2>/dev/null | grep -q "$sym"; then
|
if [[ "$syms" != *"$sym"* ]]; then
|
||||||
print_error "$arch: Missing symbol $sym"
|
print_error "$arch: Missing symbol $sym"
|
||||||
missing=1
|
missing=1
|
||||||
fi
|
fi
|
||||||
@@ -245,6 +340,52 @@ verify_jni_symbols() {
|
|||||||
|
|
||||||
if [ "$missing" -eq 0 ]; then
|
if [ "$missing" -eq 0 ]; then
|
||||||
print_success "$arch: All JNI symbols present"
|
print_success "$arch: All JNI symbols present"
|
||||||
|
else
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Hard failure: a library missing these exports still loads, and then every
|
||||||
|
# ArtiNative call throws UnsatisfiedLinkError at runtime instead.
|
||||||
|
if [ "$failed" -ne 0 ]; then
|
||||||
|
print_error "JNI exports missing — refusing to leave this .so in jniLibs"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_ndk_stamp() {
|
||||||
|
print_header "Verifying NDK stamp"
|
||||||
|
|
||||||
|
local readelf_bin
|
||||||
|
readelf_bin="$(ndk_tool readelf || true)"
|
||||||
|
if [ -z "$readelf_bin" ]; then
|
||||||
|
print_error "no readelf found (looked in the NDK and on PATH) — cannot verify the NDK stamp"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Every NDK-linked shared object carries .note.android.ident, which records
|
||||||
|
# the target API level, the NDK release name (e.g. r27d) and the NDK build
|
||||||
|
# number. Reading it back proves which toolchain actually produced the
|
||||||
|
# binary, independently of what the environment claimed — this is how the
|
||||||
|
# committed r25b libraries were identified in the first place.
|
||||||
|
for target in "${TARGETS[@]}"; do
|
||||||
|
local arch
|
||||||
|
arch="$(abi_dir_for "$target")"
|
||||||
|
local lib="$OUTPUT_DIR/$arch/$LIB_NAME"
|
||||||
|
[ -f "$lib" ] || continue
|
||||||
|
|
||||||
|
# Read the note once into a variable: `readelf | grep -q` would let grep
|
||||||
|
# exit first, kill readelf with SIGPIPE, and fail the pipeline under
|
||||||
|
# `set -o pipefail` — the same trap that made the symbol check above
|
||||||
|
# report every exported symbol as missing.
|
||||||
|
local note
|
||||||
|
note="$("$readelf_bin" -p .note.android.ident "$lib" 2>/dev/null || true)"
|
||||||
|
if grep -qw "$NDK_BUILD_NUMBER" <<< "$note"; then
|
||||||
|
print_success "$arch: built by NDK $NDK_VERSION"
|
||||||
|
else
|
||||||
|
print_error "$arch: not stamped with NDK build $NDK_BUILD_NUMBER — wrong toolchain?"
|
||||||
|
printf '%s\n' "$note"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
@@ -280,6 +421,7 @@ main() {
|
|||||||
done
|
done
|
||||||
|
|
||||||
verify_jni_symbols
|
verify_jni_symbols
|
||||||
|
verify_ndk_stamp
|
||||||
|
|
||||||
print_header "Build complete"
|
print_header "Build complete"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
# build scripts. Bump this in lockstep with ARTI_VERSION / Cargo.lock and
|
# build scripts. Bump this in lockstep with ARTI_VERSION / Cargo.lock and
|
||||||
# re-verify (see README.md → "Reproducible builds").
|
# re-verify (see README.md → "Reproducible builds").
|
||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.94.1"
|
channel = "1.98.1"
|
||||||
profile = "minimal"
|
profile = "minimal"
|
||||||
components = ["rustc", "cargo", "rust-std"]
|
components = ["rustc", "cargo", "rust-std"]
|
||||||
# Only the two ABIs we actually ship libarti_android.so for (see jniLibs/). If
|
# Only the two ABIs we actually ship libarti_android.so for (see jniLibs/). If
|
||||||
|
|||||||
+99
-54
@@ -1,6 +1,7 @@
|
|||||||
use jni::JNIEnv;
|
use jni::{jni_sig, jni_str, EnvUnowned};
|
||||||
use jni::objects::{JClass, JString, JObject, GlobalRef};
|
use jni::errors::{LogErrorAndDefault, Result as JniResult, ThrowRuntimeExAndDefault};
|
||||||
use jni::sys::{jint, jstring};
|
use jni::objects::{Global, JClass, JObject, JString};
|
||||||
|
use jni::sys::jint;
|
||||||
use jni::JavaVM;
|
use jni::JavaVM;
|
||||||
|
|
||||||
use arti_client::{BootstrapBehavior, TorClient};
|
use arti_client::{BootstrapBehavior, TorClient};
|
||||||
@@ -21,7 +22,7 @@ use anyhow::Result;
|
|||||||
static ARTI_CLIENT: Mutex<Option<Arc<TorClient<PreferredRuntime>>>> = Mutex::new(None);
|
static ARTI_CLIENT: Mutex<Option<Arc<TorClient<PreferredRuntime>>>> = Mutex::new(None);
|
||||||
static TOKIO_RUNTIME: Mutex<Option<tokio::runtime::Runtime>> = Mutex::new(None);
|
static TOKIO_RUNTIME: Mutex<Option<tokio::runtime::Runtime>> = Mutex::new(None);
|
||||||
static JAVA_VM: Mutex<Option<JavaVM>> = Mutex::new(None);
|
static JAVA_VM: Mutex<Option<JavaVM>> = Mutex::new(None);
|
||||||
static LOG_CALLBACK: Mutex<Option<GlobalRef>> = Mutex::new(None);
|
static LOG_CALLBACK: Mutex<Option<Global<JObject<'static>>>> = Mutex::new(None);
|
||||||
static SOCKS_TASK: Mutex<Option<tokio::task::JoinHandle<()>>> = Mutex::new(None);
|
static SOCKS_TASK: Mutex<Option<tokio::task::JoinHandle<()>>> = Mutex::new(None);
|
||||||
// The background directory download started by initialize(). It holds an Arc<TorClient>, so
|
// The background directory download started by initialize(). It holds an Arc<TorClient>, so
|
||||||
// destroy() must abort it too — otherwise the client cannot drop, the state file lock is never
|
// destroy() must abort it too — otherwise the client cannot drop, the state file lock is never
|
||||||
@@ -42,16 +43,21 @@ fn send_log_to_java(message: String) {
|
|||||||
let callback_opt = LOG_CALLBACK.lock().unwrap();
|
let callback_opt = LOG_CALLBACK.lock().unwrap();
|
||||||
|
|
||||||
if let (Some(vm), Some(callback)) = (vm_opt.as_ref(), callback_opt.as_ref()) {
|
if let (Some(vm), Some(callback)) = (vm_opt.as_ref(), callback_opt.as_ref()) {
|
||||||
if let Ok(mut env) = vm.attach_current_thread() {
|
// jni 0.22 only hands out an `Env` inside a closure, borrowed from an
|
||||||
|
// attachment pinned to the stack; it also pushes a local-reference frame
|
||||||
|
// per call, so `jmessage` is released when the closure returns instead of
|
||||||
|
// accumulating on this long-lived logging thread.
|
||||||
|
let _ = vm.attach_current_thread(|env| -> JniResult<()> {
|
||||||
if let Ok(jmessage) = env.new_string(&message) {
|
if let Ok(jmessage) = env.new_string(&message) {
|
||||||
let _ = env.call_method(
|
let _ = env.call_method(
|
||||||
callback.as_obj(),
|
&**callback,
|
||||||
"onLogLine",
|
jni_str!("onLogLine"),
|
||||||
"(Ljava/lang/String;)V",
|
jni_sig!("(Ljava/lang/String;)V"),
|
||||||
&[(&jmessage).into()]
|
&[(&jmessage).into()],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
Ok(())
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,52 +80,92 @@ macro_rules! log_error {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_getVersion(
|
pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_getVersion<'caller>(
|
||||||
env: JNIEnv,
|
mut env: EnvUnowned<'caller>,
|
||||||
_class: JClass,
|
_class: JClass<'caller>,
|
||||||
) -> jstring {
|
) -> JString<'caller> {
|
||||||
if JAVA_VM.lock().unwrap().is_none() {
|
// jni 0.22: the raw environment pointer is FFI-only (`EnvUnowned`); JNI calls
|
||||||
if let Ok(vm) = env.get_java_vm() {
|
// need the `Env` that `with_env` borrows for the closure. `resolve` maps an
|
||||||
*JAVA_VM.lock().unwrap() = Some(vm);
|
// `Err` to the policy — here a Java RuntimeException plus a null return —
|
||||||
|
// rather than losing it.
|
||||||
|
//
|
||||||
|
// Only the `Err` half is live: the policy's panic half runs through
|
||||||
|
// `catch_unwind`, which catches nothing under this crate's
|
||||||
|
// `panic = "abort"` release profile, so a panic in here still takes the
|
||||||
|
// process down exactly as it did before the migration.
|
||||||
|
env.with_env(|env| -> JniResult<JString<'caller>> {
|
||||||
|
if JAVA_VM.lock().unwrap().is_none() {
|
||||||
|
if let Ok(vm) = env.get_java_vm() {
|
||||||
|
*JAVA_VM.lock().unwrap() = Some(vm);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let version = format!("Arti {} (custom build with rustls)", env!("CARGO_PKG_VERSION"));
|
let version = format!("Arti {} (custom build with rustls)", env!("CARGO_PKG_VERSION"));
|
||||||
let output = env.new_string(version).expect("Couldn't create java string!");
|
env.new_string(version)
|
||||||
output.into_raw()
|
})
|
||||||
|
.resolve::<ThrowRuntimeExAndDefault>()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_setLogCallback(
|
pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_setLogCallback<'caller>(
|
||||||
env: JNIEnv,
|
mut env: EnvUnowned<'caller>,
|
||||||
_class: JClass,
|
_class: JClass<'caller>,
|
||||||
callback: JObject,
|
callback: JObject<'caller>,
|
||||||
) {
|
) {
|
||||||
if JAVA_VM.lock().unwrap().is_none() {
|
env.with_env(|env| -> JniResult<()> {
|
||||||
if let Ok(vm) = env.get_java_vm() {
|
if JAVA_VM.lock().unwrap().is_none() {
|
||||||
*JAVA_VM.lock().unwrap() = Some(vm);
|
if let Ok(vm) = env.get_java_vm() {
|
||||||
|
*JAVA_VM.lock().unwrap() = Some(vm);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(global_ref) = env.new_global_ref(callback) {
|
if let Ok(global_ref) = env.new_global_ref(&callback) {
|
||||||
*LOG_CALLBACK.lock().unwrap() = Some(global_ref);
|
*LOG_CALLBACK.lock().unwrap() = Some(global_ref);
|
||||||
log_info!("Log callback registered");
|
log_info!("Log callback registered");
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.resolve::<ThrowRuntimeExAndDefault>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize Arti runtime and bootstrap the TorClient.
|
/// Initialize Arti runtime and bootstrap the TorClient.
|
||||||
/// The TorClient is created once and reused for the app's lifetime.
|
/// The TorClient is created once and reused for the app's lifetime.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_initialize(
|
pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_initialize<'caller>(
|
||||||
mut env: JNIEnv,
|
mut env: EnvUnowned<'caller>,
|
||||||
_class: JClass,
|
_class: JClass<'caller>,
|
||||||
data_dir: JString,
|
data_dir: JString<'caller>,
|
||||||
) -> jint {
|
) -> jint {
|
||||||
if JAVA_VM.lock().unwrap().is_none() {
|
// Everything JNI-owned is read inside this closure; the rest of the function
|
||||||
if let Ok(vm) = env.get_java_vm() {
|
// is pure Rust that blocks on Tokio, which must not hold an `Env`.
|
||||||
*JAVA_VM.lock().unwrap() = Some(vm);
|
//
|
||||||
}
|
// `None` carries a failed read, because it is `Option::default()` and so is
|
||||||
}
|
// also what the policy yields for an `Err`. Both end at the same `-1` the
|
||||||
|
// old `Err` arm returned. Resolving to `jint` directly would have defaulted
|
||||||
|
// to `0`, the value this API reports as success.
|
||||||
|
//
|
||||||
|
// The already-initialized check deliberately stays *outside* the closure,
|
||||||
|
// against the whole `Option`: threading it through as a sentinel value
|
||||||
|
// would leave that sentinel to be re-tested after the closure, and a
|
||||||
|
// concurrent destroy() landing in between would let it through as the data
|
||||||
|
// directory.
|
||||||
|
let data_dir_str: Option<String> = env
|
||||||
|
.with_env(|env| -> JniResult<Option<String>> {
|
||||||
|
if JAVA_VM.lock().unwrap().is_none() {
|
||||||
|
if let Ok(vm) = env.get_java_vm() {
|
||||||
|
*JAVA_VM.lock().unwrap() = Some(vm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(match data_dir.try_to_string(env) {
|
||||||
|
Ok(s) => Some(s),
|
||||||
|
Err(e) => {
|
||||||
|
log_error!("Failed to convert data_dir: {:?}", e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.resolve::<LogErrorAndDefault>();
|
||||||
|
|
||||||
// Already initialized — skip
|
// Already initialized — skip
|
||||||
if ARTI_CLIENT.lock().unwrap().is_some() {
|
if ARTI_CLIENT.lock().unwrap().is_some() {
|
||||||
@@ -127,12 +173,9 @@ pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_initialize(
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let data_dir_str: String = match env.get_string(&data_dir) {
|
let data_dir_str: String = match data_dir_str {
|
||||||
Ok(s) => s.into(),
|
Some(s) => s,
|
||||||
Err(e) => {
|
None => return -1,
|
||||||
log_error!("Failed to convert data_dir: {:?}", e);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
log_info!("Initializing Arti with data directory: {}", data_dir_str);
|
log_info!("Initializing Arti with data directory: {}", data_dir_str);
|
||||||
@@ -220,7 +263,9 @@ pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_initialize(
|
|||||||
.create_unbootstrapped_async()
|
.create_unbootstrapped_async()
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(c) => Arc::new(c),
|
// Arti 2.4.0 made every TorClient constructor return an Arc<TorClient>
|
||||||
|
// (TorClient itself is no longer Clone), so there is nothing to wrap here.
|
||||||
|
Ok(c) => c,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log_error!("Failed to create Tor client: {:?}", e);
|
log_error!("Failed to create Tor client: {:?}", e);
|
||||||
return -3;
|
return -3;
|
||||||
@@ -267,7 +312,7 @@ pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_initialize(
|
|||||||
/// Can be called multiple times — stops any existing listener first.
|
/// Can be called multiple times — stops any existing listener first.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_startSocksProxy(
|
pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_startSocksProxy(
|
||||||
_env: JNIEnv,
|
_env: EnvUnowned,
|
||||||
_class: JClass,
|
_class: JClass,
|
||||||
port: jint,
|
port: jint,
|
||||||
) -> jint {
|
) -> jint {
|
||||||
@@ -494,7 +539,7 @@ async fn handle_socks_connection(
|
|||||||
/// Stop the SOCKS proxy listener. The TorClient stays alive.
|
/// Stop the SOCKS proxy listener. The TorClient stays alive.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_stopSocksProxy(
|
pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_stopSocksProxy(
|
||||||
_env: JNIEnv,
|
_env: EnvUnowned,
|
||||||
_class: JClass,
|
_class: JClass,
|
||||||
) -> jint {
|
) -> jint {
|
||||||
log_info!("Stopping SOCKS proxy...");
|
log_info!("Stopping SOCKS proxy...");
|
||||||
@@ -532,7 +577,7 @@ pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_stopSocksPro
|
|||||||
/// measurable signal for a guess.
|
/// measurable signal for a guess.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_bootstrapProgressPermille(
|
pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_bootstrapProgressPermille(
|
||||||
_env: JNIEnv,
|
_env: EnvUnowned,
|
||||||
_class: JClass,
|
_class: JClass,
|
||||||
) -> jint {
|
) -> jint {
|
||||||
match ARTI_CLIENT.lock().unwrap().as_ref() {
|
match ARTI_CLIENT.lock().unwrap().as_ref() {
|
||||||
@@ -550,7 +595,7 @@ pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_bootstrapPro
|
|||||||
/// leaving the UI wrong and the exit-rotation self-heal disabled.
|
/// leaving the UI wrong and the exit-rotation self-heal disabled.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_isBootstrapped(
|
pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_isBootstrapped(
|
||||||
_env: JNIEnv,
|
_env: EnvUnowned,
|
||||||
_class: JClass,
|
_class: JClass,
|
||||||
) -> jint {
|
) -> jint {
|
||||||
match ARTI_CLIENT.lock().unwrap().as_ref() {
|
match ARTI_CLIENT.lock().unwrap().as_ref() {
|
||||||
@@ -573,7 +618,7 @@ pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_isBootstrapp
|
|||||||
/// (and re-bootstrap).
|
/// (and re-bootstrap).
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_destroy(
|
pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_destroy(
|
||||||
_env: JNIEnv,
|
_env: EnvUnowned,
|
||||||
_class: JClass,
|
_class: JClass,
|
||||||
) -> jint {
|
) -> jint {
|
||||||
log_info!("Destroying Arti client");
|
log_info!("Destroying Arti client");
|
||||||
|
|||||||
@@ -12,7 +12,9 @@
|
|||||||
# ./verify-reproducible.sh # both ABIs (arm64-v8a + x86_64)
|
# ./verify-reproducible.sh # both ABIs (arm64-v8a + x86_64)
|
||||||
# ./verify-reproducible.sh --release # arm64-v8a only (faster)
|
# ./verify-reproducible.sh --release # arm64-v8a only (faster)
|
||||||
#
|
#
|
||||||
# Prerequisites are the same as build-arti.sh (rustup, cargo-ndk, Android NDK).
|
# 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.
|
# Exit 0 = reproducible, exit 1 = builds differ.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -26,15 +28,25 @@ sha256() {
|
|||||||
if command -v sha256sum >/dev/null 2>&1; then sha256sum "$@"; else shasum -a 256 "$@"; fi
|
if command -v sha256sum >/dev/null 2>&1; then sha256sum "$@"; else shasum -a 256 "$@"; fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# sha256 of every built .so, keyed by ABI dir (relative paths → stable keys).
|
# Only the ABIs this run actually rebuilds. Hashing everything under jniLibs/
|
||||||
# `find | sort` (plain text sort) is portable across GNU and BSD userlands.
|
# (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() {
|
hashes() {
|
||||||
( cd "$JNILIBS" && find . -name libarti_android.so | sort | while IFS= read -r f; do
|
( cd "$JNILIBS" && for abi in $ABIS; do
|
||||||
sha256 "$f"
|
[ -f "$abi/libarti_android.so" ] && sha256 "$abi/libarti_android.so"
|
||||||
done )
|
done )
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "### Reproducibility check for libarti_android.so"
|
echo "### Reproducibility check for libarti_android.so"
|
||||||
|
echo "### ABIs: $ABIS"
|
||||||
echo "### Canonical build path: ${ARTI_REPRO_DIR:-/tmp/amethyst-arti-build}"
|
echo "### Canonical build path: ${ARTI_REPRO_DIR:-/tmp/amethyst-arti-build}"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
@@ -59,11 +71,17 @@ fi
|
|||||||
# Informational: is the binary committed in git already the reproducible one?
|
# Informational: is the binary committed in git already the reproducible one?
|
||||||
echo
|
echo
|
||||||
echo "### vs. the committed binaries:"
|
echo "### vs. the committed binaries:"
|
||||||
if git -C "$PROJECT_ROOT" diff --quiet -- amethyst/src/main/jniLibs/; then
|
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."
|
echo "✓ The reproducible build matches what's committed — the shipped .so is verifiable as-is."
|
||||||
else
|
else
|
||||||
echo "⚠ The reproducible build differs from the committed .so (e.g. the committed one"
|
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 " predates this toolchain). Commit the rebuilt binaries so the shipped artifact"
|
||||||
echo " is itself a reproducible build:"
|
echo " is itself a reproducible build:"
|
||||||
echo " git -C \"$PROJECT_ROOT\" add amethyst/src/main/jniLibs && git commit"
|
echo " git -C \"$PROJECT_ROOT\" add$BUILT_PATHS && git commit"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user