ffi #jni_main R.2: drop native_app_glue from Android link when #jni_main present

`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.
This commit is contained in:
agra
2026-05-20 14:59:49 +03:00
parent 8ae4e0c653
commit 2461218111
2 changed files with 40 additions and 24 deletions

View File

@@ -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;
};

View File

@@ -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);