Incremental Step E (commit 4250f1b) proved that merely compiling the
Oboe C++ bridge into libwzp_desktop_lib.so — with NO Rust-side FFI
bindings, no function calls — resurrects the __init_tcb+4 / pthread_
create SIGSEGV at WryActivity.onCreate. Bisection:
build #17 (baseline) ✓
build #18 (Step A, hello.c) ✓
build #19 (Step B, wzp-client dep) ✓
build #21 (Step C, engine mod compiled) ✓
build #22 (Step D, getauxval_fix.c) ✓
build #23 (Step E, Oboe C++ compiled) ✗ — __init_tcb+4 crash
Root cause: tauri-cli hard-codes `aarch64-linux-android24-clang` as the
Rust linker. Without any C++ code in the .so, libstd's pthread_create
reference gets resolved against the dynamic libc.so. The moment we add
a C++ static library that links against libc++_shared, the link-time
resolution pulls in the API-24 libc.a static pthread_create stub — and
Rust's libstd then also calls that stub instead of libc.so's real one.
The stub calls __init_tcb which SIGSEGVs because bionic's TCB state
only exists for static-libc main executables, not .so's loaded via
dlopen. API-26 NDK has proper dynamic bindings that resolve correctly.
Option 3 fix: at image build time, replace every NDK
aarch64-linux-android24-clang (and armv7/x86_64/i686, clang/clang++)
binary with a one-line shell script that exec()s the corresponding
android26-clang. Since tauri-cli invokes the linker via absolute path,
PATH and env var overrides fail — but replacing the binary on disk
inside the image is guaranteed to take effect. The legacy wzp-android
crate doesn't need this because cargo-ndk respects .cargo/config.toml
where a crate-level linker override is set.
Only changing the Dockerfile here. Next: rebuild the image no-cache,
retry Step E, and if the baseline holds, proceed to Steps F/G.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Spent 10+ builds chasing a __init_tcb+4 / pthread_create SIGSEGV after
adding the oboe audio backend. Every "fix" made things worse. Reverting
all Android-specific files to the state at 35642d1 (build #6), which
was the last commit where the Tauri Android app actually launched,
rendered the home screen, and successfully registered on a relay.
Reverted files (all back to their 35642d1 content):
- desktop/src-tauri/Cargo.toml (no build-dep cc, no tracing-android)
- desktop/src-tauri/build.rs (git hash only, no Oboe / cc build)
- desktop/src-tauri/src/lib.rs (engine cfg-gated on non-android)
- desktop/src-tauri/src/main.rs (two-line desktop entry)
- desktop/src-tauri/src/engine.rs (desktop-only audio setup)
- scripts/Dockerfile.android-builder (no android24→26 clang shim)
- scripts/build-tauri-android.sh (no linker env vars / manifest patch)
Deleted (were added between b314138 and e2e023d):
- desktop/src-tauri/cpp/getauxval_fix.c
- desktop/src-tauri/cpp/oboe_bridge.{h,cpp}
- desktop/src-tauri/cpp/oboe_stub.cpp
- desktop/src-tauri/src/oboe_audio.rs
Next: rebuild image on remote (to drop the baked-in clang shim), build
an APK, install on Pixel 6, verify the UI renders the same way build #6
did. From there we add features back ONE at a time so we can actually
bisect which one triggers the tao::ndk_glue crash. User's rule:
"if you want to change stack, change incrementally, so we can debug".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Build #13's PATH wrapper trick failed because tauri-cli invokes the linker
with an absolute path (/opt/android-sdk/ndk/.../bin/aarch64-linux-android24-
clang), which bypasses \$PATH entirely. The pthread_shim logs confirmed the
broken API-24 stubs were still being linked:
WZP_pthread_shim: dlsym(RTLD_DEFAULT, pthread_create) returned NULL:
libdl.a is a stub --- use libdl.so instead
Move the fix up a level — into the Dockerfile itself. On image build, for
each of the four android ABIs × {clang, clang++}, rename
`${abi}24-${suffix}` to `${abi}24-${suffix}.orig` and replace it with a
shell wrapper that exec()s `${abi}26-${suffix}`. Any call to the API-24
wrapper — via PATH, absolute path, or otherwise — now transparently runs
the API-26 wrapper, which uses the real libc.so/libdl.so bindings.
The old bash-c /tmp/wrappers workaround in build-tauri-android.sh is
removed now that the image handles it at the right layer.
Also add `--shell` to build-tauri-android.sh: opens an interactive docker
container on the remote with the same mounts/env as the build, so I can
iterate on cargo tauri android build / manually patch files / etc.
without the full git push → ssh → rebuild → install loop.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dockerfile.android-builder: install Android API 36 platform + build-tools
35.0.0 alongside the existing API 34 set. Tauri 2.x mobile defaults to
compileSdk 36 / build-tools 35; without these the gradle build fails with
"SDK directory is not writable" because the read-only /opt/android-sdk
volume can't grow at build time. Adding Node.js 20, all four Rust android
targets, and tauri-cli 2.x was already in place.
scripts/build-tauri-android.sh: new build wrapper for the desktop/ Tauri
project (parallel to scripts/build-and-notify.sh which targets the legacy
android/ Kotlin app). Pulls the branch on remote, runs cargo tauri android
build inside the docker image, and sends three ntfy.sh/wzp notifications
that all carry the short git hash:
- STARTED [hash] — <commit subject>
- OK [hash] (size) — <rustypaste apk url>
- FAILED [hash] (line N) — <rustypaste log url>
On failure the full /tmp/wzp-tauri-build.log is uploaded to rustypaste so
the URL in the failure ntfy is directly downloadable, same place as the
APK.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>