From 24612181115bf4135c01ec08aca81a04c2fe5e83 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 20 May 2026 14:59:49 +0300 Subject: [PATCH] ffi #jni_main R.2: drop native_app_glue from Android link when #jni_main present MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `target.link` now takes a `has_jni_main: bool` parameter (passed by main.zig from `comp.getJniMainEmissions().len > 0`). When set: - native_app_glue.c is not compiled — no `.glue.o` produced. - `-u ANativeActivity_onCreate` is not added to the link argv. - The Java-driven Activity is the entry; the .so just provides JNI impls, bound at load time via the `JNI_OnLoad` slice R.3 will synthesize. Legacy NativeActivity builds (no `#jni_main` decl) are unchanged: glue is still compiled and `ANativeActivity_onCreate` still retained. Verified end-to-end: - #jni_main .so: `llvm-nm -D` shows neither `ANativeActivity_onCreate` nor `android_main` (correct — Java side drives entry). - Legacy .so (99-android-egl-clear): both symbols still exported. 131 host / 4 cross / zig build test all green. --- src/main.zig | 2 +- src/target.zig | 62 +++++++++++++++++++++++++++++++------------------- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/main.zig b/src/main.zig index 5474000..913f4a4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -595,7 +595,7 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons // Link (sx .o + C .o files) timer.mark(); - sx.target.link(allocator, io, obj_path, c_obj_paths, final_output, libs, fws, merged_config) catch { + sx.target.link(allocator, io, obj_path, c_obj_paths, final_output, libs, fws, merged_config, comp.getJniMainEmissions().len > 0) catch { std.debug.print("error: linking failed\n", .{}); return error.CompileError; }; diff --git a/src/target.zig b/src/target.zig index 64b40d4..121dd17 100644 --- a/src/target.zig +++ b/src/target.zig @@ -686,7 +686,7 @@ pub fn discoverAppleSdk(allocator: std.mem.Allocator, io: std.Io, sdk_name: []co return out; } -pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, extra_objects: []const []const u8, output_bin: []const u8, libraries: []const []const u8, frameworks: []const []const u8, target_config: TargetConfig) !void { +pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, extra_objects: []const []const u8, output_bin: []const u8, libraries: []const []const u8, frameworks: []const []const u8, target_config: TargetConfig, has_jni_main: bool) !void { var argv = std.ArrayList([]const u8).empty; if (target_config.isIOS()) { @@ -733,13 +733,24 @@ pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, ex while (it.next()) |part| try argv.append(allocator, part); } } else if (target_config.isAndroid()) { - // Android: NDK clang. Produces a shared library (.so) loaded by - // NativeActivity. native_app_glue.c (from the NDK) is compiled and - // linked alongside the sx code so apps can use the conventional - // `android_main(struct android_app*)` event-loop shape — the glue - // owns `ANativeActivity_onCreate` and forwards into android_main on - // a dedicated thread. `-u ANativeActivity_onCreate` keeps the glue's - // symbol from being stripped (nothing in our .o references it). + // Android: NDK clang. Produces a shared library (.so). + // + // Two entry shapes: + // + // - **#jni_main path (`has_jni_main = true`)** — the Java side + // drives lifecycle (the bundled classes.dex declares an + // Activity that overrides `onCreate` etc.). The .so just + // provides JNI implementations bound at load time via the + // `JNI_OnLoad` synthesized in slice R.3. No native_app_glue + // is needed: there's no `ANativeActivity_onCreate` to host, + // no `android_main` event loop to run. + // + // - **Legacy NativeActivity path (`has_jni_main = false`)** — + // native_app_glue.c is compiled and linked alongside the sx + // code; the glue owns `ANativeActivity_onCreate` and forwards + // into the user's `android_main` on a worker thread. The + // `-u ANativeActivity_onCreate` keeps the glue's symbol from + // being stripped (nothing in our .o references it). // // The `libraries` parameter (collected from `#library` directives) // and `frameworks` parameter (Apple-only by definition) are @@ -755,19 +766,22 @@ pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, ex const host_tag: []const u8 = if (@import("builtin").os.tag == .macos) "darwin-x86_64" else "linux-x86_64"; const clang = try std.fmt.allocPrint(allocator, "{s}/toolchains/llvm/prebuilt/{s}/bin/clang", .{ ndk_root, host_tag }); - const glue_src = try std.fmt.allocPrint(allocator, "{s}/sources/android/native_app_glue/android_native_app_glue.c", .{ndk_root}); - const glue_obj = try std.fmt.allocPrint(allocator, "{s}.glue.o", .{output_obj}); - var glue_argv = std.ArrayList([]const u8).empty; - try glue_argv.appendSlice(allocator, &.{ clang, "-c", "-fPIC" }); - if (target_config.triple) |t| { - try glue_argv.append(allocator, "-target"); - try glue_argv.append(allocator, std.mem.span(t)); - } - try glue_argv.appendSlice(allocator, &.{ glue_src, "-o", glue_obj }); - const glue_slice = try glue_argv.toOwnedSlice(allocator); - var glue_child = std.process.spawn(io, .{ .argv = glue_slice }) catch return error.LinkError; - const glue_term = glue_child.wait(io) catch return error.LinkError; - if (glue_term != .exited or glue_term.exited != 0) return error.LinkError; + const glue_obj_opt: ?[]const u8 = if (has_jni_main) null else blk: { + const glue_src = try std.fmt.allocPrint(allocator, "{s}/sources/android/native_app_glue/android_native_app_glue.c", .{ndk_root}); + const glue_obj = try std.fmt.allocPrint(allocator, "{s}.glue.o", .{output_obj}); + var glue_argv = std.ArrayList([]const u8).empty; + try glue_argv.appendSlice(allocator, &.{ clang, "-c", "-fPIC" }); + if (target_config.triple) |t| { + try glue_argv.append(allocator, "-target"); + try glue_argv.append(allocator, std.mem.span(t)); + } + try glue_argv.appendSlice(allocator, &.{ glue_src, "-o", glue_obj }); + const glue_slice = try glue_argv.toOwnedSlice(allocator); + var glue_child = std.process.spawn(io, .{ .argv = glue_slice }) catch return error.LinkError; + const glue_term = glue_child.wait(io) catch return error.LinkError; + if (glue_term != .exited or glue_term.exited != 0) return error.LinkError; + break :blk glue_obj; + }; try argv.append(allocator, clang); if (target_config.triple) |t| { @@ -776,9 +790,11 @@ pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, ex } try argv.append(allocator, "-shared"); try argv.append(allocator, "-fPIC"); - try argv.appendSlice(allocator, &.{ "-u", "ANativeActivity_onCreate" }); + if (!has_jni_main) { + try argv.appendSlice(allocator, &.{ "-u", "ANativeActivity_onCreate" }); + } try argv.append(allocator, output_obj); - try argv.append(allocator, glue_obj); + if (glue_obj_opt) |go| try argv.append(allocator, go); for (extra_objects) |eo| try argv.append(allocator, eo); try argv.append(allocator, "-o"); try argv.append(allocator, output_bin);