Skip to content

fix(android): bundle libc++_shared.so — launch crash on every device - #71

Merged
kostyk348 merged 1 commit into
mainfrom
fix/android-libcxx-launch
Nov 8, 2026
Merged

kostyk348 merged 1 commit into
mainfrom
fix/android-libcxx-launch

Conversation

@kostyk348

Copy link
Copy Markdown
Owner

Report

The Android app died instantly at launch on every device. Static ELF audit of the released v2.12.3 APK found the root cause:

  • lib/arm64-v8a/libcapsuleos_native.so has DT_NEEDED libc++_shared.so (cpal's oboe audio backend is C++),
  • but the APK packages only three native libs — libc++_shared.so is not among them.

The loader resolves dlopen dependencies eagerly, so the process died before a single line of Rust ran. cargo-ndk builds the .so but does not copy that C++ runtime from the NDK — that is the packaging gap.

Everything else checked out and is now gated: PT_LOAD alignment is 16384 (16 KB-page devices), ANativeActivity_onCreate / android_main are exported, and the manifest/backend pair matches.

Fix

  • Both android.yml and release.yml copy libc++_shared.so from the NDK sysroot into jniLibs right after cargo ndk.
  • New gate scripts/check_apk_native_deps.py (pure python, no SDK) runs against the built APK in CI: every DT_NEEDED of every packaged .so must be a system lib or packaged, alignment ≥ 16384, NativeActivity export present. Validated locally: FAIL on the v2.12.3 release APK (exact missing-lib case), LAUNCH-READY on a fixed layout.
  • versionCode bumped from the permanently stuck 1 to 2 — without this, updates refused to install over old builds («App not installed»).
  • docs/android.md: actual backend name and the libc++_shared story.

Verification

  • Local: gate = FAIL on released v2.12.3 APK, PASS on fixed layout.
  • CI: debug APK job builds and runs the gate; the v2.12.4 release job runs it on the signed APK before upload.

Type

  • Bugfix (Android launch)

…ashed at launch

Root cause (v2.12.3 and every earlier APK): cpal's oboe audio backend is
C++, so libcapsuleos_native.so carries DT_NEEDED libc++_shared.so — but
cargo-ndk does not copy that runtime from the NDK and the APK shipped
without it. The loader resolves dlopen dependencies eagerly, so the
process died before a single line of Rust ran, on every device.

Diagnosed by auditing the released APK's ELF tables with a new pure-python
gate, scripts/check_apk_native_deps.py (no SDK needed):

- every DT_NEEDED of every packaged .so must be a system lib or be
  packaged too (the v2.12.3 APK fails exactly here);
- PT_LOAD alignment must be 16384 (16 KB-page devices);
- ANativeActivity_onCreate must be exported.

Both android.yml and release.yml now copy libc++_shared.so from the NDK
sysroot into jniLibs after cargo ndk, and run the gate against the built
APK. The gate was validated locally: FAIL on the v2.12.3 release APK,
LAUNCH-READY on a fixed layout.

Also: versionCode was stuck at 1 since the first APK, so updates could
not install over old builds ('App not installed') — bumped to 2.
docs/android.md: current backend name (android-native-activity since
v2.12.0) and the libc++_shared story.
@kostyk348
kostyk348 merged commit 0f1aa6a into main Nov 8, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant