ReviewOS

craft-native/craft

fix(android): generated apps never load libcraft.so, so every action falls through to Kotlin

#200
Closed glennmichael123 opened this 9 days ago ·
9 days ago

CraftNative calls System.loadLibrary("craft") inside a try/catch and sets isAvailable = false on UnsatisfiedLinkError (CraftNative.kt.template:111-118). Every wrapper short-circuits on that, and every @JavascriptInterface method falls through to its Kotlin body — e.g. CraftNative.getDeviceInfo(activity)?.let { return it } (CraftBridge.kt.template:2341), if (CraftNative.haptic(activity, style)) return (:1491).

Nothing ever puts the library where loadLibrary would find it. Repo-wide, jniLibs appears only in docs, one build.zig comment (:2461) and one CraftNative.kt.template comment (:105). packages/android/src/index.ts copies no .so, and build.gradle.kts.app.template has no sourceSets, no ndk { abiFilters } and no packaging block.

So isAvailable is always false, and all 38 bridge_android_*.zig modules — the entire Android half of the Zig bridge migration — are built by zig build build-android and never loaded by anything.

The artifacts exist and are correctly named for it:

  • zig build build-android → zig-out/android/arm64-v8a/libcraft.so
  • zig build build-android-x86 → zig-out/android/x86_64/libcraft.so

Both are named craft, so one loadLibrary("craft") serves both ABIs (build.zig:2428-2441, :2450-2467). Neither needs the NDK: the JNI modules deliberately omit link_libc so they build with nothing but Zig (android_dispatch.zig:77-89).

Two things make this quiet rather than loud. The fallback is by design, so there is no error anywhere — an app with no library behaves exactly like an app whose library failed to bind. And there is no Zig-side log channel on Android to tell them apart (#TBD-LOG).

Worth adding an assertion that the .so is actually inside the APK, since unzip -l app-debug.apk | grep lib/x86_64/libcraft.so is the only externally checkable proof. The Android leg of the mobile E2E harness (scripts/mobile-e2e/android.ts, added in #199) reports zigDispatches: 0 and says why, so the report states which side answered rather than leaving it open — but it cannot assert the Zig path until this is fixed.

Found while building #199; the iOS side has the equivalent wiring (renderRuntimeSettings in packages/ios/src/index.ts:305-313) and the iOS E2E leg proves it works, which is what made the gap visible.

Sign in to comment on this issue.