diff --git a/src/core.zig b/src/core.zig index e1c0b62..3488006 100644 --- a/src/core.zig +++ b/src/core.zig @@ -244,6 +244,12 @@ pub const Compilation = struct { try registry.put(entry.key_ptr.*, entry.value_ptr.*.foreign_path); } + // Derive the `System.loadLibrary` argument from the `-o` basename + // (e.g. `/tmp/libsxchess.so` → `sxchess`). When `-o` is unset the + // emitter omits the static init block; the user must then arrange + // .so loading via another class. + const lib_name = libNameFromOutputPath(self.target_config.output_path); + var it = lowering.foreign_class_map.iterator(); while (it.next()) |entry| { const fcd = entry.value_ptr.*; @@ -253,7 +259,10 @@ pub const Compilation = struct { if (seen.contains(fcd.foreign_path)) continue; try seen.put(fcd.foreign_path, {}); - const java_source = try ir.jni_java_emit.emitJavaSource(self.allocator, fcd, .{ .classes = ®istry }); + const java_source = try ir.jni_java_emit.emitJavaSource(self.allocator, fcd, .{ + .classes = ®istry, + .lib_name = lib_name, + }); try self.lowering_jni_main_decls.append(self.allocator, .{ .foreign_path = try self.allocator.dupe(u8, fcd.foreign_path), .java_source = java_source, @@ -261,6 +270,16 @@ pub const Compilation = struct { } } + /// `/path/to/libfoo.so` → `foo`. Anything else → null (caller skips + /// emitting the `System.loadLibrary` init block). + fn libNameFromOutputPath(output_path: ?[]const u8) ?[]const u8 { + const path = output_path orelse return null; + const basename = std.fs.path.basename(path); + if (!std.mem.startsWith(u8, basename, "lib")) return null; + if (!std.mem.endsWith(u8, basename, ".so")) return null; + return basename[3 .. basename.len - 3]; + } + /// Java sources rendered from `#jni_main #jni_class("...")` decls during /// lowering. Empty unless `lowerToIR` has run. pub fn getJniMainEmissions(self: *const Compilation) []const JniMainEmission { diff --git a/src/ir/jni_java_emit.test.zig b/src/ir/jni_java_emit.test.zig index 36476d8..c0e8cbc 100644 --- a/src/ir/jni_java_emit.test.zig +++ b/src/ir/jni_java_emit.test.zig @@ -235,6 +235,40 @@ test "default-package class (no slash in foreign_path)" { try std.testing.expect(std.mem.indexOf(u8, out, "public class SxNoPackage") != null); } +test "lib_name renders System.loadLibrary static init block" { + const a = std.testing.allocator; + var arena = std.heap.ArenaAllocator.init(a); + defer arena.deinit(); + const aa = arena.allocator(); + + const self_ty = try makePointer(aa, try makeTypeExpr(aa, "Self")); + const body = try makeBodyMarker(aa); + const method: ast.ForeignClassMember = .{ .method = .{ + .name = "onCreate", + .params = &.{self_ty}, + .param_names = &.{"self"}, + .return_type = null, + .body = body, + } }; + + const fcd: ast.ForeignClassDecl = .{ + .name = "SxApp", + .foreign_path = "co/example/SxApp", + .runtime = .jni_class, + .is_main = true, + .members = &.{method}, + }; + + const out = try emit.emitJavaSource(a, &fcd, .{ .lib_name = "sxchess" }); + defer a.free(out); + try std.testing.expect(std.mem.indexOf(u8, out, "static { System.loadLibrary(\"sxchess\"); }") != null); + + // Without lib_name the static init is omitted. + const out2 = try emit.emitJavaSource(a, &fcd, .{}); + defer a.free(out2); + try std.testing.expect(std.mem.indexOf(u8, out2, "System.loadLibrary") == null); +} + test "#implements clauses on the class header" { const a = std.testing.allocator; var arena = std.heap.ArenaAllocator.init(a); diff --git a/src/ir/jni_java_emit.zig b/src/ir/jni_java_emit.zig index 76ae2d3..6a5f35e 100644 --- a/src/ir/jni_java_emit.zig +++ b/src/ir/jni_java_emit.zig @@ -47,6 +47,12 @@ pub const Options = struct { /// Activities — `NativeActivity` is the legacy NDK path that /// requires native_app_glue's `ANativeActivity_onCreate`. default_extends: []const u8 = "android.app.Activity", + /// `System.loadLibrary(...)` argument for the emitted static init + /// block. When set, the emitter inserts `static { System.loadLibrary + /// (lib_name); }` so JNI native delegates can resolve at runtime. + /// When null, no static init is emitted (caller must arrange .so + /// loading some other way — e.g. another class's static init). + lib_name: ?[]const u8 = null, }; /// Emit a `.java` source for the given foreign-class decl. Result is @@ -113,8 +119,14 @@ pub fn emitJavaSource( try buf.appendSlice(allocator, " {\n"); - // Two passes: @Override stubs that call super + native delegate, - // then the native declarations. + if (opts.lib_name) |ln| { + try buf.appendSlice(allocator, " static { System.loadLibrary(\""); + try buf.appendSlice(allocator, ln); + try buf.appendSlice(allocator, "\"); }\n"); + } + + // Two passes: @Override stubs + native delegate; then the native + // declarations. for (fcd.members) |m| switch (m) { .method => |md| { if (md.body == null) continue;