diff --git a/examples/ffi-jni-call-02-void.sx b/examples/ffi-jni-call-02-void.sx index ef4f5e6..4e6cfe6 100644 --- a/examples/ffi-jni-call-02-void.sx +++ b/examples/ffi-jni-call-02-void.sx @@ -21,14 +21,17 @@ #import "modules/std.sx"; #import "modules/compiler.sx"; +// Android target requires a `#jni_main` Activity declaration to +// satisfy the entry-point check. An empty stub class is enough — this +// file is testing `#jni_call` lowering, not Activity wiring. +SxJniCallVoidStub :: #jni_main #jni_class("co/swipelab/sxjnicall/SxJniCallVoidStub") { } + main :: () -> s32 { inline if OS == .android { - // Real Android entry passes env + target via android_main / - // ANativeActivity (modules/platform/android.sx). For the cross- - // compile-only test we just need the lowering to emit valid - // IR; runtime correctness is exercised by the chess - // sx_android_query_safe_insets path once Phase 1D for - // sx_android_jni.c lands. + // Real Android entry passes env + target via the user's `onCreate` + // / `#jni_attach`. For the cross-compile-only test we just need + // the lowering to emit valid IR; runtime correctness is exercised + // by the chess sx_android_query_safe_insets path. env : *void = null; target : *void = null; #jni_env(env) { @@ -40,12 +43,3 @@ main :: () -> s32 { } 0; } - -// Android target requires `android_main` as the NDK entry — kept as -// a 3-line trampoline so this example can pass through -// `--target android` builds in `tests/cross_compile.sh`. -android_main :: (app: *void) { - inline if OS == .android { - main(); - } -} diff --git a/examples/ffi-objc-call-10-os-gate.sx b/examples/ffi-objc-call-10-os-gate.sx index 1ce16dc..2a7871f 100644 --- a/examples/ffi-objc-call-10-os-gate.sx +++ b/examples/ffi-objc-call-10-os-gate.sx @@ -13,6 +13,11 @@ #import "modules/std.sx"; #import "modules/compiler.sx"; +// Empty stub class — Android cross-compile requires a `#jni_main` +// declaration to satisfy the entry-point check. This file is testing +// `inline if OS` gating around `#objc_call`, not Activity wiring. +SxObjcOsGateStub :: #jni_main #jni_class("co/swipelab/sxobjcosgate/SxObjcOsGateStub") { } + main :: () -> s32 { inline if OS == { case .ios: { @@ -32,12 +37,3 @@ main :: () -> s32 { } 0; } - -// Android target requires `android_main` as the NDK entry — kept as -// a 3-line trampoline so this example can pass through -// `--target android` builds in `tests/cross_compile.sh`. -android_main :: (app: *void) { - inline if OS == .android { - main(); - } -} diff --git a/src/ir/lower.zig b/src/ir/lower.zig index c8f453c..9e2daa0 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -28,8 +28,6 @@ const Builder = mod_mod.Builder; /// runtime resolves by name mangling — same rule. fn isExportedEntryName(name: []const u8) bool { return std.mem.eql(u8, name, "main") or - std.mem.eql(u8, name, "android_main") or - std.mem.eql(u8, name, "ANativeActivity_onCreate") or std.mem.eql(u8, name, "JNI_OnLoad") or std.mem.startsWith(u8, name, "Java_"); } @@ -231,18 +229,12 @@ pub const Lowering = struct { self.synthesizeJniMainStubs(); } - /// On Android, the OS loads the .so via one of two entry paths: - /// - /// 1. **`#jni_main` Activity** (preferred) — Java-side Activity class - /// declared as `Foo :: #jni_main #jni_class("...") { onCreate :: ... }`. - /// The Java class drives lifecycle; sx provides native delegates. - /// 2. **`android_main` trampoline** (legacy NativeActivity path) — user - /// defines `android_main(app: *void)`, native_app_glue's - /// `ANativeActivity_onCreate` jumps into it on a worker thread. - /// - /// Either satisfies the check. When both are missing, the .so won't - /// have an entry point Android can reach, so emit a diagnostic that - /// shows both options. + /// On Android, the OS loads the .so via a Java-side Activity declared + /// with `#jni_main #jni_class("...")`. The Java class drives the + /// lifecycle (onCreate / onPause / etc.) and sx provides the native + /// delegates bound via JNI name mangling. Without a `#jni_main` decl + /// there's no entry point — the .so would load but Android has nothing + /// to call into. fn checkRequiredEntryPoints(self: *Lowering) void { const tc = self.target_config orelse return; if (!tc.isAndroid()) return; @@ -253,23 +245,15 @@ pub const Lowering = struct { if (fcd.is_main and !fcd.is_foreign and fcd.runtime == .jni_class) return; } - const wanted = self.module.types.internString("android_main"); - for (self.module.functions.items) |func| { - if (func.name != wanted) continue; - if (func.is_extern) continue; - if (func.blocks.items.len == 0) continue; - return; - } - if (self.diagnostics) |diags| { diags.addFmt(.err, null, - "target is Android but no entry point declared. " ++ - "Either declare a `#jni_main` Activity class:\n\n" ++ + "target is Android but no `#jni_main` Activity declared. " ++ + "The OS launches a Java-side Activity that delegates lifecycle " ++ + "callbacks into sx — declare one like:\n\n" ++ + " Bundle :: #foreign #jni_class(\"android/os/Bundle\") {{ }}\n\n" ++ " MyApp :: #jni_main #jni_class(\"co/example/MyApp\") {{\n" ++ - " onCreate :: (self: *Self, b: *Bundle) {{ /* ... */ }};\n" ++ - " }}\n\n" ++ - "or define an `android_main` trampoline that bootstraps the " ++ - "legacy NativeActivity path (see `examples/99-android-egl-clear.sx`).", .{}); + " onCreate :: (self: *Self, b: *Bundle) {{ /* ... */ }}\n" ++ + " }}", .{}); } }