From d8968ae093523ff0a83cd7c94954029b9482cd5c Mon Sep 17 00:00:00 2001 From: agra Date: Tue, 19 May 2026 00:36:05 +0300 Subject: [PATCH] android: forward NDK sysroot to embedded clang + skip auto #library/#framework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit C imports (stb_image, stb_truetype) compiled via the embedded LLVM clang library now resolve bionic headers on Android. main.zig auto- fills target_config.sysroot with the NDK root (mirroring the iOS path that auto-fills the iOS SDK path); c_import.zig derives the bionic sysroot inside it and passes `--sysroot /toolchains/llvm/prebuilt//sysroot` to the embedded clang. Android link branch in target.zig stops auto-appending entries from the collected `#library` / `#framework` lists. Most `#library` directives in the stdlib (`objc.sx`'s `objc :: #library "objc";`, frameworks set by uikit.sx, etc.) describe Apple-specific intent that's nonsensical on Android. Users opt into Android-side libs via `opts.add_link_flag(...)` in build.sx — same shape as how the iOS branch already lists frameworks in chess's configure_build. Verified end-to-end: chess game compiles for Android, packages into a debug-signed APK, installs and launches on Pixel 7 Pro. It crashes in UIRenderer.init because raw GL calls run before EGL is up — same architectural gap iOS bridges via the Metal GPU protocol. Next step is a GLES3 GPU impl or a lazy-init in UIRenderer. 86/86 regression tests + iOS-sim chess build clean. --- src/c_import.zig | 21 +++++++++++++++++++++ src/main.zig | 10 ++++++++++ src/target.zig | 16 ++++++++++++---- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/c_import.zig b/src/c_import.zig index 8e5ab3f..2ce1769 100644 --- a/src/c_import.zig +++ b/src/c_import.zig @@ -10,6 +10,15 @@ pub const CSourceLocation = struct { line: u32, }; +/// Derive the NDK sysroot path from the NDK root (which by convention +/// lives in `target_config.sysroot` on Android — see target.zig's +/// Android link branch + main.zig's auto-discovery). Returns a NUL- +/// terminated path suitable for clang's `--sysroot ` argv. +fn androidSysrootFromNdkRoot(allocator: std.mem.Allocator, ndk_root: []const u8) ![:0]u8 { + const host_tag: []const u8 = if (builtin.os.tag == .macos) "darwin-x86_64" else "linux-x86_64"; + return try std.fmt.allocPrintSentinel(allocator, "{s}/toolchains/llvm/prebuilt/{s}/sysroot", .{ ndk_root, host_tag }, 0); +} + pub const CImportResult = struct { fn_decls: []const *Node, /// Source locations for each fn_decl (parallel array, same indices). @@ -193,6 +202,18 @@ pub fn compileCToObjects( try args_list.append(allocator, "-isysroot"); try args_list.append(allocator, (try allocator.dupeZ(u8, sr)).ptr); } + // Android: route through the NDK sysroot so bionic headers resolve. + // The embedded clang library doesn't know how to be an Android cross- + // compiler on its own. `target_config.sysroot` holds the NDK root + // by convention (main.zig auto-fills it for --target android), so + // derive the headers/libs sysroot inside it. + if (target_config.isAndroid()) { + if (target_config.sysroot) |ndk_root| { + const sysroot = try androidSysrootFromNdkRoot(allocator, ndk_root); + try args_list.append(allocator, "--sysroot"); + try args_list.append(allocator, sysroot.ptr); + } + } for (info.includes) |inc| { const dir = dirName(inc); try args_list.append(allocator, (try allocPrintZ(allocator, "-I{s}", .{dir})).ptr); diff --git a/src/main.zig b/src/main.zig index 4470fff..8f22065 100644 --- a/src/main.zig +++ b/src/main.zig @@ -167,6 +167,15 @@ pub fn main(init: std.process.Init) !void { target_config.sysroot = sx.target.discoverAppleSdk(allocator, io, sdk_name) catch null; } + // Same idea for Android — the NDK root must be visible to BOTH the + // C-import compile path (so `--sysroot ndk/.../sysroot` finds bionic + // headers) and the link path. By convention, target_config.sysroot + // holds the NDK root on Android (target.zig's link branch + c_import.zig + // both read it). Honors any explicit --sysroot. + if (target_config.isAndroid() and target_config.sysroot == null) { + target_config.sysroot = sx.target.discoverAndroidNdk(allocator, io) catch null; + } + const path = input_path orelse { printUsage(); return; @@ -559,6 +568,7 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons // Shadow the outer `fws` for the rest of the function by reassignment. fws = try merged_fws.toOwnedSlice(allocator); } + if (build_flags.len > 0) { var all_flags: std.ArrayList([]const u8) = .empty; for (target_config.extra_link_flags) |f| try all_flags.append(allocator, f); diff --git a/src/target.zig b/src/target.zig index 6be9750..e69151a 100644 --- a/src/target.zig +++ b/src/target.zig @@ -550,6 +550,14 @@ pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, ex // 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). + // + // The `libraries` parameter (collected from `#library` directives) + // and `frameworks` parameter (Apple-only by definition) are + // intentionally ignored here. On Android, users opt into specific + // libs via `opts.add_link_flag("-l")` in their build.sx — + // the platform-specific link surface should be expressed in build + // options rather than auto-inherited from every imported module + // (most of which assume Apple targets). const ndk_root = if (target_config.sysroot) |sr| try allocator.dupe(u8, sr) else @@ -587,11 +595,11 @@ pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, ex for (target_config.lib_paths) |lp| { try argv.append(allocator, try std.fmt.allocPrint(allocator, "-L{s}", .{lp})); } - for (libraries) |lib| { - try argv.append(allocator, try std.fmt.allocPrint(allocator, "-l{s}", .{lib})); - } // Default libs available on every Android runtime; linker drops - // unreferenced ones automatically. + // unreferenced ones automatically. `#library` directives are + // intentionally NOT auto-emitted here (most assume Apple targets); + // users opt in per-target via `opts.add_link_flag("-l...")` in + // their build.sx. try argv.appendSlice(allocator, &.{ "-llog", "-landroid", "-lEGL", "-lGLESv3", "-lm", "-ldl" }); for (target_config.extra_link_flags) |flag| { var it = std.mem.tokenizeScalar(u8, flag, ' ');