diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 57d6ead..3b00d2b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,15 +93,15 @@ jobs: with: version: ${{ needs.setup.outputs.zig-stable-version }} - - name: Build Test (Zig Stable) - run: zig build -Dandroid=true --verbose - working-directory: test/build + - name: Build Test + run: zig build --verbose + working-directory: test - - name: Build Minimal Example (Zig Stable) + - name: Build Minimal Example run: zig build -Dandroid=true --verbose working-directory: examples/minimal - - name: Build SDL2 Example (Zig Stable) + - name: Build SDL2 Example run: | zig build -Dandroid=true --verbose zig build -Dandroid=true -Dcrash-on-exception --verbose @@ -110,7 +110,7 @@ jobs: # NOTE(jae): 2026-04-09 # Raylib example only runs on 0.16.X due to downstream dependencies # - # - name: Build Raylib Example (Zig Stable) + # - name: Build Raylib Example # run: zig build -Dandroid=true --verbose # working-directory: examples/raylib @@ -182,21 +182,21 @@ jobs: with: version: "master" - - name: Build Test (Zig Nightly) - run: zig build -Dandroid=true --verbose - working-directory: test/build + - name: Build Test + run: zig build --verbose + working-directory: test - - name: Build Minimal Example (Zig Nightly) + - name: Build Minimal Example run: zig build -Dandroid=true --verbose working-directory: examples/minimal - - name: Build SDL2 Example (Zig Nightly) + - name: Build SDL2 Example run: zig build -Dandroid=true --verbose working-directory: examples/sdl2 # note(jae): 2026-04-09 # Downstream packages for Raylib only support 0.16.X-dev right now. - - name: Build Raylib Example (Zig Nightly) + - name: Build Raylib Example run: zig build -Dandroid=true --verbose working-directory: examples/raylib build-previous-stable: @@ -232,11 +232,11 @@ jobs: with: version: ${{ needs.setup.outputs.zig-previous-stable-version }} - - name: Build Test (Zig Nightly) - run: zig build -Dandroid=true --verbose - working-directory: test/build + - name: Build Test + run: zig build --verbose + working-directory: test - - name: Build Minimal Example + - name: Build Minimal Example run: zig build -Dandroid=true --verbose working-directory: examples/minimal diff --git a/build.zig.zon b/build.zig.zon index 1add7e8..fbef194 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -7,5 +7,6 @@ "build.zig.zon", "src", }, + .minimum_zig_version = "0.16.0", .fingerprint = 0x92bcb62d42fb2cee, } diff --git a/src/androidbuild/Apk.zig b/src/androidbuild/Apk.zig index 60ea8d6..25248bd 100644 --- a/src/androidbuild/Apk.zig +++ b/src/androidbuild/Apk.zig @@ -512,19 +512,49 @@ fn doInstallApk(apk: *Apk) Allocator.Error!*Step.InstallFile { while (iter.next()) |it| { const module = it.value_ptr.*; const root_source_file = module.root_source_file orelse continue; + const c_translate_target = module.resolved_target orelse continue; + if (!c_translate_target.result.abi.isAndroid()) continue; switch (root_source_file) { .generated => |gen| { const step = gen.file.step; - if (step.id != .translate_c) { - continue; - } - const c_translate_target = module.resolved_target orelse continue; - if (!c_translate_target.result.abi.isAndroid()) { - continue; + switch (step.id) { + .translate_c => { + // Detect if using Translate-C vendored version + // + // NOTE(jae): 2026-04-29 + // Longterm this will deprecated from Zig + + const translate_c: *std.Build.Step.TranslateC = @fieldParentPtr("step", step); + translate_c.addIncludePath(.{ .cwd_relative = apk.ndk.include_path }); + translate_c.addSystemIncludePath(.{ .cwd_relative = apk.getSystemIncludePath(c_translate_target) }); + }, + .run => { + // Detect if using Translate-C external dependency and make assumptions about the flags + // we can pass into it such as "isystem" and "-I" + // + // Name: https://codeberg.org/ziglang/translate-c/src/commit/71642ad0084d433f14b091a7b2b109f0be915dbb/build/Translator.zig#L89 + // Imports: https://codeberg.org/ziglang/translate-c/src/commit/71642ad0084d433f14b091a7b2b109f0be915dbb/build/Translator.zig#L103-L104 + if (std.mem.startsWith(u8, step.name, "translate-c ") and + (module.import_table.contains("c_builtins") and module.import_table.contains("helpers"))) + { + const run: *std.Build.Step.Run = @fieldParentPtr("step", step); + + const ndk_include_path: LazyPath = .{ .cwd_relative = apk.ndk.include_path }; + const system_include_path: LazyPath = .{ .cwd_relative = apk.getSystemIncludePath(c_translate_target) }; + + // Exposes the system include path `path` to both translate-c and to `t.mod`. + // https://codeberg.org/ziglang/translate-c/src/commit/71642ad0084d433f14b091a7b2b109f0be915dbb/build/Translator.zig#L207-L211 + module.addSystemIncludePath(system_include_path); + run.addPrefixedDirectoryArg("-isystem", system_include_path); + + // Exposes the include path `path` to both translate-c and to `t.mod`. + // https://codeberg.org/ziglang/translate-c/src/commit/71642ad0084d433f14b091a7b2b109f0be915dbb/build/Translator.zig#L203-L206 + module.addIncludePath(ndk_include_path); + run.addPrefixedDirectoryArg("-I", ndk_include_path); + } + }, + else => continue, } - const translate_c: *std.Build.Step.TranslateC = @fieldParentPtr("step", step); - translate_c.addIncludePath(.{ .cwd_relative = apk.ndk.include_path }); - translate_c.addSystemIncludePath(.{ .cwd_relative = apk.getSystemIncludePath(c_translate_target) }); }, else => continue, } diff --git a/src/androidbuild/androidbuild.zig b/src/androidbuild/androidbuild.zig index 7740768..49a12b5 100644 --- a/src/androidbuild/androidbuild.zig +++ b/src/androidbuild/androidbuild.zig @@ -107,6 +107,7 @@ pub fn standardTargets(b: *std.Build, target: ResolvedTarget) []ResolvedTarget { // Seperated logic into "resolveTargets" so that consumers of this library can create this option themselves and use "b.lazyImport" // See: https://github.com/silbinarywolf/zig-android-sdk/pull/82 const all_targets = b.option(bool, "android", "Build for all Android targets (x86, x86_64, aarch64, arm, etc)") orelse false; + return resolveTargets(b, .{ .default_target = target, .all_targets = all_targets, diff --git a/test/build.zig b/test/build.zig new file mode 100644 index 0000000..0f6e6be --- /dev/null +++ b/test/build.zig @@ -0,0 +1,46 @@ +const Build = @import("std").Build; +const builtin = @import("builtin"); +const eql = @import("std").mem.eql; + +/// Make sure this is a stable version of Zig +const is_latest_stable_zig = builtin.zig_version.pre == null and + builtin.zig_version.major == 0 and builtin.zig_version.minor >= 16; + +pub fn build(b: *Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const all_step = b.step("all", "Run all tests"); + b.default_step = all_step; + + for (b.available_deps) |available_dep| { + const test_name, _ = available_dep; + const test_case_dep_step: *Build.Step = blk: { + if (is_latest_stable_zig) { + const dep = b.dependency(test_name, .{ + .target = target, + .optimize = optimize, + .android = true, + }); + break :blk dep.builder.default_step; + } else { + // Skip translate_c_dep as it requires Zig 0.16.0 stable as of 2026-04-29 + if (eql(u8, test_name, "translate_c_dep")) { + continue; + } + + // NOTE(jae): 2026-04-29 + // Due to b.dependency() trying to always load Zig dependencies regardless + // of version requirements, we make other Zig versions invoke build manually + // so that ignoring dependency logic is respected. + const cmd = b.addSystemCommand(&.{ "zig", "build", "-Dandroid=true" }); + cmd.setCwd(b.path(test_name)); + cmd.expectExitCode(0); + break :blk &cmd.step; + } + }; + const test_step = b.step(test_name, b.fmt("Run the '{s}' test", .{test_name})); + test_step.dependOn(test_case_dep_step); + all_step.dependOn(test_step); + } +} diff --git a/test/build.zig.zon b/test/build.zig.zon new file mode 100644 index 0000000..3e6d565 --- /dev/null +++ b/test/build.zig.zon @@ -0,0 +1,11 @@ +.{ + .name = .tests, + .version = "0.0.0", + .fingerprint = 0x1260fc5e4c176d8b, + .paths = .{""}, + .dependencies = .{ + .build = .{ .path = "build" }, + .lazy_android = .{ .path = "lazy_android" }, + .translate_c_dep = .{ .path = "translate_c_dep" }, + }, +} diff --git a/test/build/build.zig b/test/build/build.zig index 304964f..20bff25 100644 --- a/test/build/build.zig +++ b/test/build/build.zig @@ -6,19 +6,20 @@ const std = @import("std"); const builtin = @import("builtin"); +const log = std.log.scoped(.build); const android = @import("android"); +/// Make sure this is a stable version of Zig +const is_latest_stable_zig = builtin.zig_version.pre == null and + builtin.zig_version.major == 0 and builtin.zig_version.minor >= 16; + pub fn build(b: *std.Build) void { const exe_name: []const u8 = "build_test"; const root_target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const android_targets = android.standardTargets(b, root_target); - // NOTE(jae): 2026-04-12 - // Run it *after* the "standardTargets" call - testLazyImportAndResolveTargets(b, root_target); - var root_target_single = [_]std.Build.ResolvedTarget{root_target}; const targets: []std.Build.ResolvedTarget = if (android_targets.len == 0) root_target_single[0..] @@ -46,77 +47,91 @@ pub fn build(b: *std.Build) void { }; for (targets) |target| { + if (!target.result.abi.isAndroid()) { + std.debug.panic("For testing Android builds only. Target(s) should be Android not: {t}", .{target.result.abi}); + } + + const translate_c_vendored_mod = testTranslateCVendor(b, target, optimize) orelse return; + const app_module = b.createModule(.{ .target = target, .optimize = optimize, .root_source_file = b.path("src/build_test_main.zig"), + .imports = &.{ + .{ + .name = "translate_c_internal", + .module = translate_c_vendored_mod, + }, + }, }); - var exe: *std.Build.Step.Compile = if (target.result.abi.isAndroid()) b.addLibrary(.{ + const libmain = b.addLibrary(.{ .name = "main", .root_module = app_module, .linkage = .dynamic, - }) else b.addExecutable(.{ - .name = exe_name, - .root_module = app_module, }); - // if building as library for Android, add this target - // NOTE: Android has different CPU targets so you need to build a version of your - // code for x86, x86_64, arm, arm64 and more - if (target.result.abi.isAndroid()) { - const apk: *android.Apk = android_apk orelse @panic("Android APK should be initialized"); - const android_dep = b.dependency("android", .{ - .optimize = optimize, - .target = target, - }); - exe.root_module.addImport("android", android_dep.module("android")); - - apk.addArtifact(exe); - } else { - b.installArtifact(exe); - - // If only 1 target, add "run" step - if (targets.len == 1) { - const run_step = b.step("run", "Run the application"); - const run_cmd = b.addRunArtifact(exe); - run_step.dependOn(&run_cmd.step); - } - } + const apk: *android.Apk = android_apk orelse @panic("Android APK should be initialized"); + const android_dep = b.dependency("android", .{ + .optimize = optimize, + .target = target, + }); + libmain.root_module.addImport("android", android_dep.module("android")); + + apk.addArtifact(libmain); } if (android_apk) |apk| { testInstallAndAddRunStep(b, apk); } } -/// Test calling lazyImport and then calling "resolveTargets" -/// -/// PR: https://github.com/silbinarywolf/zig-android-sdk/pull/83 -fn testLazyImportAndResolveTargets(b: *std.Build, root_target: std.Build.ResolvedTarget) void { - const all_android_targets = true; - const android_targets: []std.Build.ResolvedTarget = blk: { - if (all_android_targets or root_target.result.abi.isAndroid()) { - if (b.lazyImport(@This(), "lazy_android")) |lazy_android| { - break :blk lazy_android.resolveTargets(b, .{ - .default_target = root_target, - .all_targets = true, - }); - } - } - break :blk &[0]std.Build.ResolvedTarget{}; - }; - if (android_targets.len != 4) @panic("expected 'resolveTargets' it to return 4 Android targets"); +/// Test the Translate-C vendored copy, this will eventually be deprecated and removed from Zig but for now exists in 0.16.X +fn testTranslateCVendor(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) ?*std.Build.Module { + const trans_c = b.addTranslateC(.{ + .root_source_file = b.addWriteFiles().add("android_c.h", + \\#include + ), + .target = target, + .optimize = optimize, + }); + return trans_c.createModule(); +} + +/// Test the Translate-C external dependency version +fn testTranslateCExternal(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) ?*std.Build.Module { + const translate_c_dep_name = if (comptime builtin.zig_version.major == 0 and builtin.zig_version.minor == 15) + "translate_c_previous_stable" + else + "translate_c_stable"; + const translate_c_import = b.lazyImport(@This(), translate_c_dep_name) orelse return null; + const translate_c = b.lazyDependency(translate_c_dep_name, .{}) orelse return null; + const Translator = translate_c_import.Translator; + + const trans_libandroid: Translator = .init(translate_c, .{ + .c_source_file = b.addWriteFiles().add("android_c.h", + \\#include + ), + .target = target, + .optimize = optimize, + }); + return trans_libandroid.mod; } /// Test the addLibraryFile functionality /// /// Requested feature here: https://github.com/silbinarywolf/zig-android-sdk/issues/77 fn testAddLibraryFile(b: *std.Build, apk: *android.Apk) void { + if (!is_latest_stable_zig) { + return; + } + const vulkan_validation_dep = b.lazyDependency("vulkan_validation", .{}) orelse return; apk.addLibraryFile(.arm64_v8a, vulkan_validation_dep.path("arm64-v8a/libVkLayer_khronos_validation.so")); apk.addLibraryFile(.armeabi_v7a, vulkan_validation_dep.path("armeabi-v7a/libVkLayer_khronos_validation.so")); apk.addLibraryFile(.x86, vulkan_validation_dep.path("x86/libVkLayer_khronos_validation.so")); apk.addLibraryFile(.x86_64, vulkan_validation_dep.path("x86_64/libVkLayer_khronos_validation.so")); + + log.info("testAddLibraryFile: add vulkan validation layers to APK", .{}); } fn testInstallAndAddRunStep(b: *std.Build, apk: *android.Apk) void { diff --git a/test/build/build.zig.zon b/test/build/build.zig.zon index 8023e3a..8489e1c 100644 --- a/test/build/build.zig.zon +++ b/test/build/build.zig.zon @@ -5,10 +5,6 @@ .android = .{ .path = "../..", }, - .lazy_android = .{ - .path = "../..", - .lazy = true, - }, .vulkan_validation = .{ .url = "https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-1.4.341.0/android-binaries-1.4.341.0.zip", .hash = "N-V-__8AABTXlAV0z_BGl5-lZeOEm_d2gHEhExT2qjxMqQ72", diff --git a/test/build/src/build_test_main.zig b/test/build/src/build_test_main.zig index 1023159..28c8da7 100644 --- a/test/build/src/build_test_main.zig +++ b/test/build/src/build_test_main.zig @@ -4,6 +4,11 @@ const builtin = @import("builtin"); const android = @import("android"); +comptime { + // For vendored translate-c usage, validate at compile-time that this symbol exists + _ = @import("translate_c_internal").__android_log_write; +} + const androidbind = @import("android-bind.zig"); /// custom standard options for Android diff --git a/test/lazy_android/build.zig b/test/lazy_android/build.zig new file mode 100644 index 0000000..09eb9d1 --- /dev/null +++ b/test/lazy_android/build.zig @@ -0,0 +1,30 @@ +const Build = @import("std").Build; +const log = @import("std").log.scoped(.lazy_android); + +pub fn build(b: *Build) void { + const root_target = b.standardTargetOptions(.{}); + _ = b.standardOptimizeOption(.{}); + + // Test calling lazyImport and then calling "resolveTargets" + // + // PR: https://github.com/silbinarywolf/zig-android-sdk/pull/83 + { + const all_android_targets = b.option(bool, "android", "Custom usage of android flag") orelse false; + if (!all_android_targets) + @panic("expected android=true for the flag"); + const android_targets: []Build.ResolvedTarget = blk: { + if (all_android_targets or root_target.result.abi.isAndroid()) { + if (b.lazyImport(@This(), "lazy_android")) |lazy_android| { + break :blk lazy_android.resolveTargets(b, .{ + .default_target = root_target, + .all_targets = true, + }); + } + } + break :blk &[0]Build.ResolvedTarget{}; + }; + if (android_targets.len != 4) @panic("expected 'resolveTargets' it to return 4 Android targets"); + + log.info("testLazyImportAndResolveTargets: check that resolving android targets worked. Got: {}", .{android_targets.len}); + } +} diff --git a/test/lazy_android/build.zig.zon b/test/lazy_android/build.zig.zon new file mode 100644 index 0000000..bcf3b63 --- /dev/null +++ b/test/lazy_android/build.zig.zon @@ -0,0 +1,15 @@ +.{ + .name = .lazy_android, + .version = "0.0.0", + .fingerprint = 0xf96a46f8318f02b1, + .dependencies = .{ + .lazy_android = .{ + .path = "../..", + .lazy = true, + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + }, +} diff --git a/test/translate_c_dep/android/AndroidManifest.xml b/test/translate_c_dep/android/AndroidManifest.xml new file mode 100644 index 0000000..d10678a --- /dev/null +++ b/test/translate_c_dep/android/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + diff --git a/test/translate_c_dep/android/res/mipmap/ic_launcher.png b/test/translate_c_dep/android/res/mipmap/ic_launcher.png new file mode 100644 index 0000000..7ea4bd8 Binary files /dev/null and b/test/translate_c_dep/android/res/mipmap/ic_launcher.png differ diff --git a/test/translate_c_dep/android/res/values/strings.xml b/test/translate_c_dep/android/res/values/strings.xml new file mode 100644 index 0000000..e5c8190 --- /dev/null +++ b/test/translate_c_dep/android/res/values/strings.xml @@ -0,0 +1,10 @@ + + + + Zig Build Test + + com.zig.translate_c_dep + diff --git a/test/translate_c_dep/build.zig b/test/translate_c_dep/build.zig new file mode 100644 index 0000000..3f896a1 --- /dev/null +++ b/test/translate_c_dep/build.zig @@ -0,0 +1,96 @@ +//! Test that using translate-c as a dependency works with the Zig Android SDK + +const std = @import("std"); +const builtin = @import("builtin"); +const log = std.log.scoped(.build); + +const android = @import("android"); + +/// Make sure this is a stable version of Zig +const is_latest_stable_zig = builtin.zig_version.pre == null and + builtin.zig_version.major == 0 and builtin.zig_version.minor >= 16; + +pub fn build(b: *std.Build) void { + const exe_name: []const u8 = "translate_c_test"; + const root_target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const android_targets = android.standardTargets(b, root_target); + + if (!is_latest_stable_zig) { + log.warn("skipping translate-c as dependency test for Zig {}", .{builtin.zig_version_string}); + return; + } + + var root_target_single = [_]std.Build.ResolvedTarget{root_target}; + const targets: []std.Build.ResolvedTarget = if (android_targets.len == 0) + root_target_single[0..] + else + android_targets; + + const android_apk: ?*android.Apk = blk: { + if (android_targets.len == 0) break :blk null; + + const android_sdk = android.Sdk.create(b, .{}); + const apk = android_sdk.createApk(.{ + .name = exe_name, + .api_level = .android15, + .build_tools_version = "35.0.1", + .ndk_version = "29.0.13113456", + }); + const key_store_file = android_sdk.createKeyStore(.example); + apk.setKeyStore(key_store_file); + apk.setAndroidManifest(b.path("android/AndroidManifest.xml")); + apk.addResourceDirectory(b.path("android/res")); + + break :blk apk; + }; + + for (targets) |target| { + if (!target.result.abi.isAndroid()) { + std.debug.panic("For testing Android builds only. Target(s) should be Android not: {t}", .{target.result.abi}); + } + + const app_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("src/translate_c_dep_main.zig"), + }); + + // Must be stable release of Zig *and* 0.16.X or higher + { + const translate_c_external_mod = testTranslateCExternal(b, target, optimize) orelse return; + app_module.addImport("translate_c_external", translate_c_external_mod); + log.info("testTranslateCExternal: add import 'translate_c_external' to {t}", .{target.result.cpu.arch}); + } + + const libmain = b.addLibrary(.{ + .name = "main", + .root_module = app_module, + .linkage = .dynamic, + }); + + const apk: *android.Apk = android_apk orelse @panic("Android APK should be initialized"); + apk.addArtifact(libmain); + } + if (android_apk) |apk| { + const installed_apk = apk.addInstallApk(); + b.getInstallStep().dependOn(&installed_apk.step); + } +} + +/// Test the Translate-C external dependency version +fn testTranslateCExternal(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) ?*std.Build.Module { + const translate_c_dep_name = "translate_c"; + const translate_c_import = b.lazyImport(@This(), translate_c_dep_name) orelse return null; + const translate_c = b.lazyDependency(translate_c_dep_name, .{}) orelse return null; + const Translator = translate_c_import.Translator; + + const trans_libandroid: Translator = .init(translate_c, .{ + .c_source_file = b.addWriteFiles().add("android_c.h", + \\#include + ), + .target = target, + .optimize = optimize, + }); + return trans_libandroid.mod; +} diff --git a/test/translate_c_dep/build.zig.zon b/test/translate_c_dep/build.zig.zon new file mode 100644 index 0000000..d5e065e --- /dev/null +++ b/test/translate_c_dep/build.zig.zon @@ -0,0 +1,26 @@ +.{ + .name = .translate_c_dep, + .version = "0.0.0", + .fingerprint = 0xe316eba7796ae687, + .zig_version = "0.16.0", + .dependencies = .{ + .android = .{ + .path = "../..", + }, + .translate_c = .{ + .url = "git+https://codeberg.org/ziglang/translate-c#7a1a9fdc4ab00835748a6657ecbb835e3d5d45f7", + .hash = "translate_c-0.0.0-Q_BUWvP1BgCjAk6PWv5286tOlvzD9-X-NkuTzh0KxY0Q", + }, + // NOTE(jae): 2026-04-29 + // Cannot keep Zig 0.15.X version in without breaking this, would need a seperate test + // .translate_c_previous_stable = .{ + // .url = "git+https://codeberg.org/ziglang/translate-c#cb8973a121a6de43d3ebc6808cd8070d716e7ea1", + // .hash = "translate_c-0.0.0-Q_BUWveCBgAzseZyfMxV_u8lBHp5SaWD8HOk8jSr_UtT", + // .lazy = true, + // }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + }, +} diff --git a/test/translate_c_dep/src/translate_c_dep_main.zig b/test/translate_c_dep/src/translate_c_dep_main.zig new file mode 100644 index 0000000..1eb7bb4 --- /dev/null +++ b/test/translate_c_dep/src/translate_c_dep_main.zig @@ -0,0 +1,8 @@ +const builtin = @import("builtin"); + +const android = @import("android"); + +comptime { + // For external translate-c usage, validate at compile-time that this symbol exists + _ = @import("translate_c_external").__android_log_write; +}