diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b00d2b..b133385 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,7 +114,7 @@ jobs: # run: zig build -Dandroid=true --verbose # working-directory: examples/raylib - # TODO(jae): 2025-03-30 + # NOTE(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 # if the stdout of the command has 'Monkey aborted due to error.' diff --git a/examples/minimal/src/android-bind.zig b/examples/minimal/src/android-bind.zig index 0fd96f5..206cb72 100644 --- a/examples/minimal/src/android-bind.zig +++ b/examples/minimal/src/android-bind.zig @@ -1,4 +1,4 @@ -// TODO(jae): 2024-09-19 +// NOTE(jae): 2024-09-19 // Consider just making this import from native C libraries. // For now just wanted a basic example that compiles and runs on an Android emulator diff --git a/examples/sdl2/build.zig b/examples/sdl2/build.zig index cce4f66..0ec346f 100644 --- a/examples/sdl2/build.zig +++ b/examples/sdl2/build.zig @@ -54,8 +54,15 @@ pub fn build(b: *std.Build) void { .target = android_targets[0], }); const sdl_java_files = sdl_dep.namedWriteFiles("sdljava"); - for (sdl_java_files.files.items) |file| { - apk.addJavaSourceFile(.{ .file = file.contents.copy }); + if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 16) { + // Deprecated: Zig 0.16.X and lower + for (sdl_java_files.files.items) |file| { + apk.addJavaSourceFile(.{ .file = file.contents.copy }); + } + } else { + for (sdl_java_files.copies.items) |copy| { + apk.addJavaSourceFile(.{ .file = copy.src_file }); + } } break :blk apk; }; diff --git a/src/android/Logger.zig b/src/android/Logger.zig index bfcd214..20f156f 100644 --- a/src/android/Logger.zig +++ b/src/android/Logger.zig @@ -15,7 +15,7 @@ const Writer = std.Io.Writer; /// logs with the package name. /// /// To workaround this, we bake the package name into the Zig binaries. -const package_name: [:0]const u8 = @import("android_builtin").package_name; +const package_name: [:0]const u8 = std.fmt.comptimePrint("{s}", .{@import("android_builtin").package_name}); level: Level, writer: Writer, diff --git a/src/android/android.zig b/src/android/android.zig index 069061e..6cffb0b 100644 --- a/src/android/android.zig +++ b/src/android/android.zig @@ -48,8 +48,11 @@ fn androidLogFn( .err => .err, // android.ANDROID_LOG_WARN = 6, }; - const fields_info = args_type_info.@"struct".fields; - if (fields_info.len == 0 and + const fields_len: comptime_int = if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 16) + args_type_info.@"struct".fields.len + else + args_type_info.@"struct".field_names.len; + if (fields_len == 0 and comptime std.mem.indexOfScalar(u8, format, '{') == null) { // If no formatting, log string directly with Android logging diff --git a/src/android/android_builtin.zig b/src/android/android_builtin.zig index a0b851f..408c308 100644 --- a/src/android/android_builtin.zig +++ b/src/android/android_builtin.zig @@ -1,2 +1,2 @@ //! Stub that is replaced by build system -pub const package_name: [:0]const u8 = ""; +pub const package_name: []const u8 = ""; diff --git a/src/android/zig016/zig016.zig b/src/android/zig016/zig016.zig index e661aa6..6cb89f6 100644 --- a/src/android/zig016/zig016.zig +++ b/src/android/zig016/zig016.zig @@ -5,7 +5,10 @@ const builtin = @import("builtin"); const ndk = @import("ndk"); const android_builtin = @import("android_builtin"); -const package_name: ?[*:0]const u8 = if (android_builtin.package_name.len > 0) android_builtin.package_name else null; +const package_name: ?[*:0]const u8 = if (android_builtin.package_name.len > 0) + std.fmt.comptimePrint("{s}", .{android_builtin.package_name}) +else + null; const LogFunction = fn (comptime message_level: std.log.Level, comptime scope: @EnumLiteral(), comptime format: []const u8, args: anytype) void; diff --git a/src/androidbuild/Apk.zig b/src/androidbuild/Apk.zig index 257ca1c..29bf7d9 100644 --- a/src/androidbuild/Apk.zig +++ b/src/androidbuild/Apk.zig @@ -157,7 +157,13 @@ pub const AddJavaSourceFileOption = struct { /// of your APK. pub fn addJavaSourceFile(apk: *Apk, options: AddJavaSourceFileOption) void { const b = apk.b; - apk.java_files.append(b.allocator, options.file.dupe(b)) catch @panic("OOM"); + const java_file = if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 16) + // Deprecated: Just uses Build + options.file.dupe(b) + else + options.file.dupe(b.graph); + + apk.java_files.append(b.allocator, java_file) catch @panic("OOM"); } pub const AddJavaSourceFilesOptions = struct { @@ -268,8 +274,8 @@ fn doInstallApk(apk: *Apk) Allocator.Error!*Step.InstallFile { @panic("call setAndroidManifestFile and point to your AndroidManifest.xml file"); }; - // TODO(jae): 2024-10-01 - // Add option where you can explicitly set an optional release mode with like: + // NOTE(jae): 2024-10-01 + // Consider adding option where you can explicitly set an optional release mode with like: // - setMode(.debug) // // If that value ISN'T set then we can just infer based on optimization level. @@ -314,8 +320,11 @@ fn doInstallApk(apk: *Apk) Allocator.Error!*Step.InstallFile { aapt2link.addArg("-I"); aapt2link.addFileArg(root_jar); - if (b.verbose) { - aapt2link.addArg("-v"); + if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 16) { + // Deprecated: b.verbose no longer exists + if (b.verbose) { + aapt2link.addArg("-v"); + } } // Inserts android:debuggable="true" in to the application node of the manifest, @@ -392,7 +401,7 @@ fn doInstallApk(apk: *Apk) Allocator.Error!*Step.InstallFile { break :blk resources_apk_file; }; - const package_name_file = blk: { + const package_name_file: LazyPath = blk: { const aapt2packagename = b.addSystemCommand(&[_][]const u8{ apk.build_tools.aapt2, "dump", @@ -403,7 +412,7 @@ fn doInstallApk(apk: *Apk) Allocator.Error!*Step.InstallFile { const aapt2_package_name_file = if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 15) aapt2packagename.captureStdOut() else - aapt2packagename.captureStdOut(.{}); + aapt2packagename.captureStdOut(.{ .trim_whitespace = .trailing }); break :blk aapt2_package_name_file; }; @@ -437,6 +446,8 @@ fn doInstallApk(apk: *Apk) Allocator.Error!*Step.InstallFile { .cwd_relative => |sub_path| sub_path, .generated => @panic("invalid precompiled library, cannot be generated"), .dependency => |dep| dep.sub_path, + // TODO(jae): 2026-06-29: Handle .relative in Zig 0.17.X + // .relative => @panic("UNHANDLED: invalid relative path"), }); _ = apk_files.addCopyFile(precompiled_library.path, b.fmt("lib/{s}/{s}", .{ so_dir, precompiled_lib_basename })); } @@ -484,8 +495,17 @@ fn doInstallApk(apk: *Apk) Allocator.Error!*Step.InstallFile { if (!c_translate_target.result.abi.isAndroid()) continue; switch (root_source_file) { .generated => |gen| { - const step = gen.file.step; - switch (step.id) { + const step: *std.Build.Step = if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 16) + // Deprecated: Zig 0.16.X, used to get Step directly + gen.file.step + else + b.graph.generated_files.items[@intFromEnum(gen.index)]; + const tag = if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 16) + // Deprecated: Zig 0.16.X, renamed to tag in later versions + step.id + else + step.tag; + switch (tag) { .translate_c => { // Detect if using Translate-C vendored version // @@ -609,10 +629,12 @@ fn doInstallApk(apk: *Apk) Allocator.Error!*Step.InstallFile { javac_cmd.addArg("-cp"); javac_cmd.addFileArg(root_jar); - // NOTE(jae): 2026-03-01 - // If we have verbose logging on, telling us about deprecated Java files - if (b.verbose) { - javac_cmd.addArg("-Xlint:deprecation"); + if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 16) { + // NOTE(jae): 2026-03-01 + // If we have verbose logging on, telling us about deprecated Java files + if (b.verbose) { + javac_cmd.addArg("-Xlint:deprecation"); + } } // Output directory @@ -641,7 +663,12 @@ fn doInstallApk(apk: *Apk) Allocator.Error!*Step.InstallFile { // d8.addArg(number_as_string); // add each output *.class file - D8Glob.create(b, d8, java_classes_output_dir, root_jar); + if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 16) { + D8Glob.create(b, d8, java_classes_output_dir, root_jar); + } else { + // TODO(jae): 2026-06-29: Update D8Glob to collect files as seperate Run artifact + @compileError("TODO: Rewrite D8Glob to use Step.Run"); + } // ie. android_sdk/platforms/android-{api-level}/android.jar d8.addArg("--lib"); @@ -662,7 +689,8 @@ fn doInstallApk(apk: *Apk) Allocator.Error!*Step.InstallFile { apk.sdk.java_tools.jar, }); jar.setName(runNameContext("jar (unzip resources.apk)")); - if (b.verbose) { + + if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 16 and b.verbose) { jar.addArg("--verbose"); } @@ -720,7 +748,7 @@ fn doInstallApk(apk: *Apk) Allocator.Error!*Step.InstallFile { // -M, --no-manifest = Do not create a manifest file for the entries // -0, --no-compress = Store only; use no ZIP compression const compress_zip_arg = "-cfM"; - if (b.verbose) jar.addArg(compress_zip_arg ++ "v") else jar.addArg(compress_zip_arg); + if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 16 and b.verbose) jar.addArg(compress_zip_arg ++ "v") else jar.addArg(compress_zip_arg); const output_zip_file = jar.addOutputFileArg("compiled_code.zip"); jar.addArg("."); @@ -747,7 +775,10 @@ fn doInstallApk(apk: *Apk) Allocator.Error!*Step.InstallFile { // -M, --no-manifest = Do not create a manifest file for the entries // -0, --no-compress = Store only; use no ZIP compression const update_zip_arg = "-ufM0"; - if (b.verbose) jar.addArg(update_zip_arg ++ "v") else jar.addArg(update_zip_arg); + if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 16 and b.verbose) + jar.addArg(update_zip_arg ++ "v") + else + jar.addArg(update_zip_arg); jar.addFileArg(zip_file); jar.addArg("."); break :blk &jar.step; @@ -777,7 +808,7 @@ fn doInstallApk(apk: *Apk) Allocator.Error!*Step.InstallFile { // Source: https://developer.android.com/tools/zipalign (10th Sept, 2024) // // Example: "zipalign -P 16 -f -v 4 infile.apk outfile.apk" - if (b.verbose) { + if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 16 and b.verbose) { zipalign.addArg("-v"); } zipalign.addArgs(&.{ @@ -1072,7 +1103,7 @@ fn updateSharedLibraryOptions(artifact: *std.Build.Step.Compile) void { artifact.root_module.strip = optimize == .ReleaseSmall; } - // TODO(jae): 2024-09-19 - Copy-pasted from https://github.com/ikskuh/ZigAndroidTemplate/blob/master/Sdk.zig + // NOTE(jae): 2024-09-19 - Copy-pasted from https://github.com/ikskuh/ZigAndroidTemplate/blob/master/Sdk.zig // Remove when https://github.com/ziglang/zig/issues/7935 is resolved. if (artifact.root_module.resolved_target) |target| { if (target.result.cpu.arch == .x86) { @@ -1121,9 +1152,20 @@ fn updatePathWithJdk(apk: *Apk, run: *std.Build.Step.Run) Allocator.Error!void { b.fmt("{s}/bin", .{apk.sdk.jdk_path}), path, }); + // NOTE(jae): 2026-06-29 + // Old version allowed mutating the env_map directly try env_map.put("PATH", new_path); - } else { + } else if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 16) { + // Deprecated: addPathDir was removed run.addPathDir(b.pathJoin(&.{ apk.sdk.jdk_path, "bin" })); + } else { + var env_map = run.getEnvMap(); + const path = env_map.get("PATH") orelse &[0]u8{}; + const new_path = try std.mem.join(b.allocator, &[1]u8{std.fs.path.delimiter}, &.{ + b.fmt("{s}/bin", .{apk.sdk.jdk_path}), + path, + }); + run.setEnvironmentVariable("PATH", new_path); } } diff --git a/src/androidbuild/BuildTools.zig b/src/androidbuild/BuildTools.zig index b7df126..057cd1b 100644 --- a/src/androidbuild/BuildTools.zig +++ b/src/androidbuild/BuildTools.zig @@ -28,10 +28,6 @@ pub fn init(b: *std.Build, android_sdk_path: []const u8, build_tools_version: [] // ie. $ANDROID_HOME/build-tools/35.0.0 const build_tools_path = b.pathResolve(&[_][]const u8{ android_sdk_path, "build-tools", build_tools_version }); - // TODO(jae): 2025-05-24 - // We could validate build_tool_version to ensure its 3 numbers with dots seperating - // ie. "35.0.0" - // Check if build tools path is accessible // ie. $ANDROID_HOME/build-tools/35.0.0 const access_wrapped_error = if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 15) diff --git a/src/androidbuild/BuiltinOptionsUpdate.zig b/src/androidbuild/BuiltinOptionsUpdate.zig index 7ac513f..7593b1d 100644 --- a/src/androidbuild/BuiltinOptionsUpdate.zig +++ b/src/androidbuild/BuiltinOptionsUpdate.zig @@ -10,31 +10,37 @@ const fs = @import("std").fs; const mem = @import("std").mem; const assert = @import("std").debug.assert; -pub const base_id: Step.Id = .custom; - -step: Step, - +step: if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 16) Step else void, options: *Options, package_name_stdout: LazyPath, pub fn create(owner: *Build, package_name_stdout: LazyPath) *BuiltinOptionsUpdate { - const options = Build.addOptions(owner); - const builtin_options_update = owner.allocator.create(BuiltinOptionsUpdate) catch @panic("OOM"); - builtin_options_update.* = .{ - .step = Step.init(.{ - .id = base_id, - .name = androidbuild.runNameContext("builtin_options_update"), - .owner = owner, - .makeFn = make, - }), - .options = options, - .package_name_stdout = package_name_stdout, - }; - // Run step relies on this finishing - options.step.dependOn(&builtin_options_update.step); - // Depend on package name stdout before running this step - package_name_stdout.addStepDependencies(&builtin_options_update.step); + + if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 16) { + // Deprecated: Make Zig 0.15.X use this approach + builtin_options_update.* = .{ + .step = Step.init(.{ + .id = .custom, + .name = androidbuild.runNameContext("builtin_options_update"), + .owner = owner, + .makeFn = deprecatedZig016OrLowerMake, + }), + .options = Build.addOptions(owner), + .package_name_stdout = package_name_stdout, + }; + // Run step relies on this finishing + builtin_options_update.options.step.dependOn(&builtin_options_update.step); + // Depend on package name stdout before running this step + package_name_stdout.addStepDependencies(&builtin_options_update.step); + } else { + builtin_options_update.* = .{ + .step = {}, + .options = Build.addOptions(owner), + .package_name_stdout = package_name_stdout, + }; + builtin_options_update.options.addOptionPath("package_name", package_name_stdout); + } return builtin_options_update; } @@ -42,7 +48,7 @@ pub fn createModule(self: *BuiltinOptionsUpdate) *Build.Module { return self.options.createModule(); } -fn make(step: *Step, _: Build.Step.MakeOptions) !void { +fn deprecatedZig016OrLowerMake(step: *Step, _: Build.Step.MakeOptions) !void { const b = step.owner; const builtin_options_update: *BuiltinOptionsUpdate = @fieldParentPtr("step", step); const options = builtin_options_update.options; diff --git a/src/androidbuild/DirectoryFileInput.zig b/src/androidbuild/DirectoryFileInput.zig index 08f9357..8bf396f 100644 --- a/src/androidbuild/DirectoryFileInput.zig +++ b/src/androidbuild/DirectoryFileInput.zig @@ -28,22 +28,28 @@ const FileInputRange = struct { }; pub fn create(owner: *Build, run: *Run, dir: LazyPath) void { - const self = owner.allocator.create(DirectoryFileInput) catch @panic("OOM"); - self.* = .{ - .step = Step.init(.{ - .id = .custom, - .name = androidbuild.runNameContext("directory-file-input"), - .owner = owner, - .makeFn = make, - }), - .run = run, - .dir = dir, - .file_input_range = null, - }; - // Run step relies on DirectoryFileInput finishing - run.step.dependOn(&self.step); - // If dir is generated then this will wait for that dir to generate - dir.addStepDependencies(&self.step); + if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 16) { + // Deprecated: Zig 0.16.X and lower supported custom Make steps + const self = owner.allocator.create(DirectoryFileInput) catch @panic("OOM"); + self.* = .{ + .step = Step.init(.{ + .id = .custom, + .name = androidbuild.runNameContext("directory-file-input"), + .owner = owner, + .makeFn = make, + }), + .run = run, + .dir = dir, + .file_input_range = null, + }; + // Run step relies on DirectoryFileInput finishing + run.step.dependOn(&self.step); + // If dir is generated then this will wait for that dir to generate + dir.addStepDependencies(&self.step); + } else { + // TODO(jae): 2026-06-29: Find all files in directory input + @compileError("TODO: Update this in the same way I've updated D8Glob"); + } } fn make(step: *Step, options: Build.Step.MakeOptions) !void { diff --git a/src/androidbuild/androidbuild.zig b/src/androidbuild/androidbuild.zig index 49a12b5..cc5680b 100644 --- a/src/androidbuild/androidbuild.zig +++ b/src/androidbuild/androidbuild.zig @@ -254,8 +254,8 @@ const AndroidTargetQuery = struct { .abi = if (android_target.cpu_arch != .arm) .android else .androideabi, .cpu_arch = android_target.cpu_arch, .cpu_features_add = android_target.cpu_features_add, - // TODO(jae): 2025-05-11 - // Setup Android API Level for Zig 0.14.0+ + // NOTE(jae): 2025-05-11 + // Consider setting Android API Level for Zig 0.14.0+ // .android_api_level = null, }; } diff --git a/src/androidbuild/tools.zig b/src/androidbuild/tools.zig index b9f3a41..03bfbc3 100644 --- a/src/androidbuild/tools.zig +++ b/src/androidbuild/tools.zig @@ -178,10 +178,6 @@ pub fn createApk(sdk: *Sdk, options: Apk.Options) *Apk { return Apk.create(sdk, options); } -// TODO: Consider adding step to run: sdkmanager --install "ndk;21.3.6528147" -// pub fn installNdkVersion(ndk_version: []const u8) *Step { -// } - /// Start an installed application on your Android device or emulator. /// To install an APK first see "addAdbInstall" /// @@ -193,7 +189,7 @@ pub fn addAdbStart(sdk: *Sdk, package_name_and_java_entry: []const u8) *Step.Run if (sdk.platform_tools.adb.len == 0) { @panic("Cannot call addAdbStart as 'adb' is not installed"); } - // TODO: Improve this to be its own special Step that can auto-detect the "com.zig.sdl2/com.zig.sdl2.ZigSDLActivity" data + // NOTE(jae): Improve this to be its own special Step that can auto-detect the "com.zig.sdl2/com.zig.sdl2.ZigSDLActivity" data const adb_shell_start = b.addSystemCommand(&.{ sdk.platform_tools.adb, "shell", "am", "start", "-S", "-W", "-n", package_name_and_java_entry }); return adb_shell_start; } @@ -547,15 +543,30 @@ const PathSearch = struct { // &b.graph.environ_map; const maybe_user: ?[]const u8 = environ_map.get("USER") orelse null; if (maybe_user) |user| { - const jarsigner_path = b.findProgram(&.{"jarsigner"}, &.{ - // NOTE(jae): 2026-01-10 - // I manually put my install here, not standard per-se but I see no reason to not support this. - b.fmt("/home/{s}/android-studio/jbr/bin", .{user}), - // NOTE(jae): 2026-01-10 - // Suggested install locations for Android Studio from: https://developer.android.com/studio/install - "/usr/local/android-studio/jbr/bin", // for your user profile - "/opt/android-studio/jbr/bin", // for shared users - }) catch break :jdkpath null; + const maybe_jarsigner_path: ?[]const u8 = if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 16) + b.findProgram(&.{"jarsigner"}, &.{ + // NOTE(jae): 2026-01-10 + // I manually put my install here, not standard per-se but I see no reason to not support this. + b.fmt("/home/{s}/android-studio/jbr/bin", .{user}), + // NOTE(jae): 2026-01-10 + // Suggested install locations for Android Studio from: https://developer.android.com/studio/install + "/usr/local/android-studio/jbr/bin", // for your user profile + "/opt/android-studio/jbr/bin", // for shared users + }) catch null + else + b.findProgram(.{ + .names = &.{ + "jarsigner", + // NOTE(jae): 2026-01-10 + // I manually put my install here, not standard per-se but I see no reason to not support this. + b.fmt("/home/{s}/android-studio/jbr/bin/jarsigner", .{user}), + // NOTE(jae): 2026-01-10 + // Suggested install locations for Android Studio from: https://developer.android.com/studio/install + "/usr/local/android-studio/jbr/bin/jarsigner", // for your user profile + "/opt/android-studio/jbr/bin/jarsigner", // for shared users + }, + }); + const jarsigner_path = maybe_jarsigner_path orelse break :jdkpath null; const jbr_bin_dir = std.fs.path.dirname(jarsigner_path) orelse break :jdkpath null; const jbr_dir = std.fs.path.dirname(jbr_bin_dir) orelse break :jdkpath null; break :jdkpath jbr_dir; diff --git a/test/build/build.zig b/test/build/build.zig index 20bff25..a1d5ec4 100644 --- a/test/build/build.zig +++ b/test/build/build.zig @@ -1,7 +1,7 @@ //! This module is for testing that we implemented certain build features and to at least make sure //! there is code coverage for new APIs added. //! -//! TODO(Jae): 2026-04-12 +//! NOTE(Jae): 2026-04-12 //! Ideally adding functions to also validate the output APK file would be nice too. const std = @import("std"); diff --git a/test/build/src/android-bind.zig b/test/build/src/android-bind.zig index 0fd96f5..206cb72 100644 --- a/test/build/src/android-bind.zig +++ b/test/build/src/android-bind.zig @@ -1,4 +1,4 @@ -// TODO(jae): 2024-09-19 +// NOTE(jae): 2024-09-19 // Consider just making this import from native C libraries. // For now just wanted a basic example that compiles and runs on an Android emulator