diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 967b441..4531cb1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -103,9 +103,12 @@ jobs: zig build -Dandroid=true -Dcrash-on-exception --verbose working-directory: examples/sdl2 - - name: Build Raylib Example (Zig Stable) - run: zig build -Dandroid=true --verbose - working-directory: examples/raylib + # NOTE(jae): 2026-04-09 + # Raylib example only runs on 0.16.X due to downstream dependencies + # + # - name: Build Raylib Example (Zig Stable) + # run: zig build -Dandroid=true --verbose + # working-directory: examples/raylib # TODO(jae): 2025-03-30 # Need to figure out how to get 'adb shell monkey' to return an error code or be able to return an error code @@ -183,12 +186,11 @@ jobs: run: zig build -Dandroid=true --verbose working-directory: examples/sdl2 - # note(jae): 2026-01-10 - # Downstream packages for Raylib won't work with Zig 0.15.2 *and* Zig 0.16.X - # - # - name: Build Raylib Example (Zig Nightly) - # run: zig build -Dandroid=true --verbose - # working-directory: examples/raylib + # note(jae): 2026-04-09 + # Downstream packages for Raylib only support 0.16.X-dev right now. + - name: Build Raylib Example (Zig Nightly) + run: zig build -Dandroid=true --verbose + working-directory: examples/raylib build-previous-stable: name: Build Zig Previous needs: [setup] diff --git a/examples/raylib/build.zig b/examples/raylib/build.zig index 201652a..c45b6de 100644 --- a/examples/raylib/build.zig +++ b/examples/raylib/build.zig @@ -1,9 +1,11 @@ const std = @import("std"); -const android = @import("android"); const LinkMode = std.builtin.LinkMode; +const android = @import("android"); + +const exe_name = "raylib"; + pub fn build(b: *std.Build) void { - const exe_name = "raylib"; const root_target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const android_targets = android.standardTargets(b, root_target); @@ -47,6 +49,10 @@ pub fn build(b: *std.Build) void { .optimize = optimize, .android_api_version = @as([]const u8, b.fmt("{}", .{@intFromEnum(apk.api_level)})), .android_ndk = @as([]const u8, apk.ndk.path), + // NOTE(jae): 2026-04-09 + // Must be statically built for Android, otherwise "android_main" is not handled here: + // https://github.com/raysan5/raylib/blob/f89d38b086c1d0a0c7e38c9c648aa91c05646300/src/platforms/rcore_android.c#L322 + .linkage = LinkMode.static, }) else b.dependency("raylib_zig", .{ @@ -55,7 +61,14 @@ pub fn build(b: *std.Build) void { .linkage = LinkMode.dynamic, }); - app.linkLibrary(raylib_dep.artifact("raylib")); + const raylib_lib = raylib_dep.artifact("raylib"); + // NOTE(jae): 2026-04-09 + // Enable PIC=true for Raylib, otherwise it will not compile. + // + // ld.lld: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'CORE'; recompile with -fPIC + // relocation R_X86_64_64 cannot be used against local symbol; recompile with -fPIC + raylib_lib.root_module.pic = true; + app.linkLibrary(raylib_lib); app.addImport("raylib", raylib_dep.module("raylib")); if (android_apk) |apk| { @@ -71,12 +84,15 @@ pub fn build(b: *std.Build) void { app.addIncludePath(native_app_glue_dir); apk.addArtifact(b.addLibrary(.{ - .linkage = .dynamic, .name = "main", .root_module = app, + .linkage = .dynamic, })); } else { - const exe = b.addExecutable(.{ .name = exe_name, .root_module = app }); + const exe = b.addExecutable(.{ + .name = exe_name, + .root_module = app, + }); b.installArtifact(exe); const run_exe = b.addRunArtifact(exe); diff --git a/examples/raylib/build.zig.zon b/examples/raylib/build.zig.zon index 68a52c7..70a0b4d 100644 --- a/examples/raylib/build.zig.zon +++ b/examples/raylib/build.zig.zon @@ -1,13 +1,13 @@ .{ .name = .raylib, .version = "0.0.0", - .minimum_zig_version = "0.14.0", + .minimum_zig_version = "0.16.0", .fingerprint = 0x13035e5cf42e313f, .dependencies = .{ .android = .{ .path = "../.." }, .raylib_zig = .{ - .url = "git+https://github.com/raylib-zig/raylib-zig#cd71c85d571027ac8033357f83b124ee051825b3", - .hash = "raylib_zig-5.6.0-dev-KE8REENOBQC-m5nK7M2b5aKSIubJPbPLUYcRhT7aT3RN", + .url = "git+https://github.com/raylib-zig/raylib-zig#a93d7f04aeeb138efc0a08b406aad74cdea17c1c", + .hash = "raylib_zig-5.6.0-dev-KE8REJ9mBQC9yBwvG5aBLUgMaGVGyzbrpyGZNUyzwvUI", }, }, .paths = .{ diff --git a/examples/raylib/src/main.zig b/examples/raylib/src/main.zig index 6617613..ef0d06f 100644 --- a/examples/raylib/src/main.zig +++ b/examples/raylib/src/main.zig @@ -1,5 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); + const android = @import("android"); const rl = @import("raylib"); @@ -35,12 +36,35 @@ else comptime { if (builtin.abi.isAndroid()) { - // Setup exported C-function as defined in AndroidManifest.xml - // ie. + // Setup exported C-function as defined in AndroidManifest.xml and Raylib. + // + // Android knows to natively call this library + // - + // + // Then Raylib makes the "android_main" entrypoint call the exported "main" C-function + // https://github.com/raysan5/raylib/blob/f89d38b086c1d0a0c7e38c9c648aa91c05646300/src/platforms/rcore_android.c#L322 @export(&androidMain, .{ .name = "main" }); + + // NOTE(jae): 2026-04-12 + // As of March 2026, Raylib requires a linker flag to make __real_fopen exist + // https://github.com/raysan5/raylib/pull/5624 + // + // Because Zig doesn't give access to linker flags to add (-Wl,--wrap=fopen), we just export __real_fopen ourselves and call it here: + // -Wl,--wrap=fopen + // Related comment: https://github.com/raysan5/raylib/blob/f89d38b086c1d0a0c7e38c9c648aa91c05646300/src/platforms/rcore_android.c#L299-L300 + @export(&raylibFileOpen, .{ .name = "__real_fopen" }); } } +fn raylibFileOpen(filename: [*c]const u8, modes: [*c]const u8) callconv(.c) ?*anyopaque { + return @import("std").c.fopen(filename, modes); +} + fn androidMain() callconv(.c) c_int { - return std.start.callMain(); + main() catch |err| { + std.log.err("{t}", .{err}); + if (@errorReturnTrace()) |trace| std.debug.dumpStackTrace(trace); + return 1; + }; + return 0; } diff --git a/src/androidbuild/Apk.zig b/src/androidbuild/Apk.zig index e127ad6..b345467 100644 --- a/src/androidbuild/Apk.zig +++ b/src/androidbuild/Apk.zig @@ -456,31 +456,9 @@ fn doInstallApk(apk: *Apk) Allocator.Error!*Step.InstallFile { // - i686-linux-android // - x86_64-linux-android for (apk.artifacts.items, 0..) |artifact, artifact_index| { - const target: ResolvedTarget = artifact.root_module.resolved_target orelse { + if (artifact.root_module.resolved_target == null) { @panic(b.fmt("artifact[{d}] has no 'target' set", .{artifact_index})); - }; - - // NOTE(jae): 2026-02-01 - // If not explicitly set in users build, default to using LLVM and LLD for Android builds - // as that's the same toolchain that the Android SDK uses. - // - // This also can resolve issues with Zigs linker not yet supporting certain compression schemes/etc - if (artifact.use_llvm == null) { - artifact.use_llvm = true; } - if (artifact.use_lld == null) { - artifact.use_lld = true; - } - - // https://developer.android.com/ndk/guides/abis#native-code-in-app-packages - const so_dir: []const u8 = switch (target.result.cpu.arch) { - .aarch64 => "arm64-v8a", - .arm => "armeabi-v7a", - .x86_64 => "x86_64", - .x86 => "x86", - else => @panic(b.fmt("unsupported or unhandled arch: {s}", .{@tagName(target.result.cpu.arch)})), - }; - _ = apk_files.addCopyFile(artifact.getEmittedBin(), b.fmt("lib/{s}/lib{s}.so", .{ so_dir, artifact.name })); // Add module // - If a module has no `root_source_file` (e.g you're only compiling C files using `addCSourceFiles`) @@ -523,25 +501,21 @@ fn doInstallApk(apk: *Apk) Allocator.Error!*Step.InstallFile { // update linked libraries that use C or C++ to: // - use Android LibC file // - add Android NDK library paths. (libandroid, liblog, etc) - apk.updateLinkObjects(artifact, so_dir, apk_files); + apk.updateLinkObjects(artifact, apk_files); // update artifact to: // - Be configured to work correctly on Android // - To know where C header /lib files are via setLibCFile and linkLibC // - Provide path to additional libraries to link to { - if (artifact.linkage) |linkage| { - if (linkage == .dynamic) { - updateSharedLibraryOptions(artifact); - } + if (artifact.root_module.link_libc == null) { + artifact.root_module.link_libc = true; } - apk.setLibCFile(artifact); - apk.addLibraryPaths(artifact.root_module); - artifact.root_module.link_libc = true; + apk.updateArtifact(artifact, apk_files); // Apply workaround for Zig 0.14.0 stable // - // This *must* occur after "apk.updateLinkObjects" for the root package otherwise + // This *must* occur after apk.updateArtifact (apk.updateLinkObjects) for the root package otherwise // you may get an error like: "unable to find dynamic system library 'c++abi_zig_workaround'" apk.applyLibLinkCppWorkaroundIssue19(artifact); } @@ -787,36 +761,58 @@ fn setLibCFile(apk: *Apk, compile: *Step.Compile) void { compile.setLibCFile(android_libc_path); } -fn updateLinkObjects(apk: *Apk, root_artifact: *Step.Compile, so_dir: []const u8, raw_top_level_apk_files: *Step.WriteFile) void { +fn updateArtifact(apk: *Apk, artifact: *Step.Compile, raw_top_level_apk_files: *Step.WriteFile) void { const b = apk.b; + + // If you have a library that is being built as an *.so then install it + // alongside your library. + // + // This was initially added to support building SDL2 with Zig. + if (artifact.linkage) |linkage| { + if (linkage == .dynamic) { + updateSharedLibraryOptions(artifact); + + // https://developer.android.com/ndk/guides/abis#native-code-in-app-packages + const target = artifact.root_module.resolved_target orelse unreachable; + const so_dir = androidbuild.getTargetLibDir(b, target); + _ = raw_top_level_apk_files.addCopyFile(artifact.getEmittedBin(), b.fmt("lib/{s}/lib{s}.so", .{ so_dir, artifact.name })); + } + } + + // NOTE(jae): 2026-02-01 + // If not explicitly set in users build, default to using LLVM and LLD for Android builds + // as that's the same toolchain that the Android SDK uses. + // + // This also can resolve issues with Zigs linker not yet supporting certain compression schemes/etc + if (artifact.use_lld == null) { + artifact.use_lld = true; + } + if (artifact.use_llvm == null) { + artifact.use_llvm = true; + } + + // If library is built using C or C++ then setLibCFile + if (artifact.root_module.link_libc == true or + artifact.root_module.link_libcpp == true) + { + apk.setLibCFile(artifact); + } + + // Add library paths to find "android", "log", etc + apk.addLibraryPaths(artifact.root_module); +} + +fn updateLinkObjects(apk: *Apk, root_artifact: *Step.Compile, raw_top_level_apk_files: *Step.WriteFile) void { for (root_artifact.root_module.link_objects.items) |link_object| { switch (link_object) { .other_step => |artifact| { switch (artifact.kind) { .lib => { - // If you have a library that is being built as an *.so then install it - // alongside your library. - // - // This was initially added to support building SDL2 with Zig. - if (artifact.linkage) |linkage| { - if (linkage == .dynamic) { - updateSharedLibraryOptions(artifact); - _ = raw_top_level_apk_files.addCopyFile(artifact.getEmittedBin(), b.fmt("lib/{s}/lib{s}.so", .{ so_dir, artifact.name })); - } - } - - // If library is built using C or C++ then setLibCFile - if (artifact.root_module.link_libc == true or - artifact.root_module.link_libcpp == true) - { - apk.setLibCFile(artifact); - } - - // Add library paths to find "android", "log", etc - apk.addLibraryPaths(artifact.root_module); + // Update updateSharedLibraryOptions, libCFile, addLibraryPaths + apk.updateArtifact(artifact, raw_top_level_apk_files); // Update libraries linked to this library - apk.updateLinkObjects(artifact, so_dir, raw_top_level_apk_files); + apk.updateLinkObjects(artifact, raw_top_level_apk_files); // Apply workaround for Zig 0.14.0 and Zig 0.15.X apk.applyLibLinkCppWorkaroundIssue19(artifact); diff --git a/src/androidbuild/androidbuild.zig b/src/androidbuild/androidbuild.zig index facb30a..90e176b 100644 --- a/src/androidbuild/androidbuild.zig +++ b/src/androidbuild/androidbuild.zig @@ -109,6 +109,29 @@ pub fn resolveTargets(b: *std.Build, options: ResolveTargetOptions) []ResolvedTa @panic(b.fmt("unsupported Android target given: {s}", .{linuxTriple})); } +/// Get the library directory name of a given build target within an APK. +/// - armeabi-v7a +/// - arm64-v8a +/// - x86_64 +/// - x86 +/// +/// Example of structure within an APK +/// - lib/armeabi-v7a/libfoo.so +/// - lib/arm64-v8a/libfoo.so +/// - lib/x86/libfoo.so +/// - lib/x86_64/libfoo.so +/// +/// See documentation here: https://developer.android.com/ndk/guides/abis#native-code-in-app-packages +pub fn getTargetLibDir(b: *std.Build, target: ResolvedTarget) []const u8 { + return switch (target.result.cpu.arch) { + .aarch64 => "arm64-v8a", + .arm => "armeabi-v7a", + .x86_64 => "x86_64", + .x86 => "x86", + else => @panic(b.fmt("cannot determine APK library directory, unsupported or unhandled arch: {s}", .{@tagName(target.result.cpu.arch)})), + }; +} + fn getAllAndroidTargets(b: *std.Build) []ResolvedTarget { const resolved_targets = b.allocator.alloc(ResolvedTarget, supported_android_targets.len) catch @panic("OOM"); for (supported_android_targets, 0..) |android_target, i| {