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| {