From 2f97a8a68a5568e5942033be54edfcfacaf28a60 Mon Sep 17 00:00:00 2001 From: Jae B Date: Sun, 1 Mar 2026 15:31:28 +1100 Subject: [PATCH 1/5] improve builtin options update to work with --watch --- src/androidbuild/apk.zig | 3 +-- src/androidbuild/builtin_options_update.zig | 30 +++++++++++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/androidbuild/apk.zig b/src/androidbuild/apk.zig index 3728cca..39f82a1 100644 --- a/src/androidbuild/apk.zig +++ b/src/androidbuild/apk.zig @@ -423,8 +423,7 @@ fn doInstallApk(apk: *Apk) Allocator.Error!*Step.InstallFile { }; const android_builtin = blk: { - const android_builtin_options = std.Build.addOptions(b); - BuiltinOptionsUpdate.create(b, android_builtin_options, package_name_file); + const android_builtin_options = BuiltinOptionsUpdate.create(b, package_name_file); break :blk android_builtin_options.createModule(); }; diff --git a/src/androidbuild/builtin_options_update.zig b/src/androidbuild/builtin_options_update.zig index d54fea5..7ac513f 100644 --- a/src/androidbuild/builtin_options_update.zig +++ b/src/androidbuild/builtin_options_update.zig @@ -1,15 +1,14 @@ //! BuiltinOptionsUpdate will update the *Options -const std = @import("std"); const androidbuild = @import("androidbuild.zig"); const builtin = @import("builtin"); -const Build = std.Build; +const Build = @import("std").Build; const Step = Build.Step; const Options = Build.Step.Options; const LazyPath = Build.LazyPath; -const fs = std.fs; -const mem = std.mem; -const assert = std.debug.assert; +const fs = @import("std").fs; +const mem = @import("std").mem; +const assert = @import("std").debug.assert; pub const base_id: Step.Id = .custom; @@ -18,7 +17,9 @@ step: Step, options: *Options, package_name_stdout: LazyPath, -pub fn create(owner: *std.Build, options: *Options, package_name_stdout: LazyPath) void { +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(.{ @@ -34,6 +35,11 @@ pub fn create(owner: *std.Build, options: *Options, package_name_stdout: LazyPat options.step.dependOn(&builtin_options_update.step); // Depend on package name stdout before running this step package_name_stdout.addStepDependencies(&builtin_options_update.step); + return builtin_options_update; +} + +pub fn createModule(self: *BuiltinOptionsUpdate) *Build.Module { + return self.options.createModule(); } fn make(step: *Step, _: Build.Step.MakeOptions) !void { @@ -41,6 +47,14 @@ fn make(step: *Step, _: Build.Step.MakeOptions) !void { const builtin_options_update: *BuiltinOptionsUpdate = @fieldParentPtr("step", step); const options = builtin_options_update.options; + // If using --watch and the user updated AndroidManifest.xml, this step can be re-triggered. + // + // To avoid appending multiple "package_name = " lines to the output module, we need to clear it if + // the options step has any contents + if (options.contents.items.len > 0) { + options.contents.clearRetainingCapacity(); + } + const package_name_path = if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 15) builtin_options_update.package_name_stdout.getPath3(b, step) else @@ -56,9 +70,9 @@ fn make(step: *Step, _: Build.Step.MakeOptions) !void { else try package_name_path.root_dir.handle.readFile(b.graph.io, package_name_path.sub_path, package_name_backing_buf); const package_name_stripped = if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 14) - std.mem.trimRight(u8, package_name_filedata, " \r\n") + mem.trimRight(u8, package_name_filedata, " \r\n") else - std.mem.trimEnd(u8, package_name_filedata, " \r\n"); + mem.trimEnd(u8, package_name_filedata, " \r\n"); const package_name: [:0]const u8 = try b.allocator.dupeZ(u8, package_name_stripped); options.addOption([:0]const u8, "package_name", package_name); From 3c9edd395e1dd8d5120887340051bb52108b757c Mon Sep 17 00:00:00 2001 From: Jae B Date: Sun, 1 Mar 2026 15:45:45 +1100 Subject: [PATCH 2/5] fix --watch for DirectoryFileInput --- src/androidbuild/DirectoryFileInput.zig | 83 ++++++++++++++++++++----- 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/src/androidbuild/DirectoryFileInput.zig b/src/androidbuild/DirectoryFileInput.zig index 9715575..995c1ce 100644 --- a/src/androidbuild/DirectoryFileInput.zig +++ b/src/androidbuild/DirectoryFileInput.zig @@ -1,16 +1,15 @@ //! DirectoryFileInput adds files within a directory to the dependencies of the given Step.Run command //! This is required so that generated directories will work. -const std = @import("std"); const androidbuild = @import("androidbuild.zig"); const builtin = @import("builtin"); -const Build = std.Build; +const Build = @import("std").Build; const Step = Build.Step; const Run = Build.Step.Run; const LazyPath = Build.LazyPath; -const fs = std.fs; -const mem = std.mem; -const assert = std.debug.assert; +const fs = @import("std").fs; +const mem = @import("std").mem; +const debug = @import("std").debug; step: Step, @@ -20,7 +19,15 @@ run: *Build.Step.Run, /// The directory that will contain the files to glob dir: LazyPath, -pub fn create(owner: *std.Build, run: *Run, dir: LazyPath) void { +/// Track the files added to the Run step for --watch +file_input_range: ?FileInputRange, + +const FileInputRange = struct { + start_value: []const u8, + len: u32, +}; + +pub fn create(owner: *Build, run: *Run, dir: LazyPath) void { const self = owner.allocator.create(DirectoryFileInput) catch @panic("OOM"); self.* = .{ .step = Step.init(.{ @@ -31,6 +38,7 @@ pub fn create(owner: *std.Build, run: *Run, dir: LazyPath) void { }), .run = run, .dir = dir, + .file_input_range = null, }; // Run step relies on DirectoryFileInput finishing run.step.dependOn(&self.step); @@ -38,12 +46,35 @@ pub fn create(owner: *std.Build, run: *Run, dir: LazyPath) void { dir.addStepDependencies(&self.step); } -fn make(step: *Step, _: Build.Step.MakeOptions) !void { +fn make(step: *Step, options: Build.Step.MakeOptions) !void { const b = step.owner; + const gpa = options.gpa; const arena = b.allocator; const self: *DirectoryFileInput = @fieldParentPtr("step", step); - const run = self.run; + + // Add the directory to --watch input so that if any files are updated or changed + // this step will re-trigger + const need_derived_inputs = try step.addDirectoryWatchInput(self.dir); + + // triggers on --watch if a file is modified. + if (self.file_input_range) |file_input_range| { + const start_index: usize = blk: { + for (run.file_inputs.items, 0..) |lp, file_input_index| { + switch (lp) { + .cwd_relative => |cwd_relative| { + if (mem.eql(u8, file_input_range.start_value, cwd_relative)) { + break :blk file_input_index; + } + }, + else => continue, + } + } + return error.MissingFileInputWatchArgument; + }; + try run.file_inputs.replaceRange(run.step.owner.allocator, start_index, file_input_range.len, &.{}); + } + const dir_path = if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 15) self.dir.getPath3(b, step) else @@ -60,6 +91,8 @@ fn make(step: *Step, _: Build.Step.MakeOptions) !void { else dir.close(b.graph.io); + var optional_file_input_value: ?[]const u8 = null; + var optional_file_input_start_index: ?usize = null; var walker = try dir.walk(arena); defer walker.deinit(); while (if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 15) @@ -67,12 +100,34 @@ fn make(step: *Step, _: Build.Step.MakeOptions) !void { else try walker.next(b.graph.io)) |entry| { - if (entry.kind != .file) continue; - - // Add file as dependency to run command - run.addFileInput(LazyPath{ - .cwd_relative = try dir_path.root_dir.join(b.allocator, &.{ dir_path.sub_path, entry.path }), - }); + switch (entry.kind) { + .directory => { + if (need_derived_inputs) { + const entry_path = try dir_path.join(arena, entry.path); + try step.addDirectoryWatchInputFromPath(entry_path); + } + }, + .file => { + // Add file as dependency to run command + const file_path = try dir_path.root_dir.join(gpa, &.{ dir_path.sub_path, entry.path }); + if (optional_file_input_value == null) { + // Set index and value of first file + optional_file_input_start_index = run.file_inputs.items.len; + optional_file_input_value = file_path; + } + run.addFileInput(LazyPath{ + .cwd_relative = file_path, + }); + }, + else => continue, + } + } + if (optional_file_input_value) |file_input_value| { + const file_input_start_index = optional_file_input_start_index orelse unreachable; + self.file_input_range = .{ + .start_value = file_input_value, + .len = @intCast(run.file_inputs.items.len - file_input_start_index), + }; } } From 3dd37eacd5daf6377bb2c5eb746687614f090052 Mon Sep 17 00:00:00 2001 From: Jae B Date: Sun, 1 Mar 2026 16:01:28 +1100 Subject: [PATCH 3/5] update d8glob logic to also trim FileInput list --- src/androidbuild/d8glob.zig | 97 ++++++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 35 deletions(-) diff --git a/src/androidbuild/d8glob.zig b/src/androidbuild/d8glob.zig index f9a2c1a..89384b7 100644 --- a/src/androidbuild/d8glob.zig +++ b/src/androidbuild/d8glob.zig @@ -27,8 +27,9 @@ argv_range: ?ArgRange, const file_ext = ".class"; const ArgRange = struct { - start: usize, - end: usize, + argv_start: usize, + file_input_start: usize, + len: u32, }; /// Creates a D8Glob step which is used to collect all *.class output files after a javac process generates them @@ -58,20 +59,28 @@ fn make(step: *Step, options: Build.Step.MakeOptions) !void { const b = step.owner; const arena = b.allocator; const gpa = b.allocator; - const glob: *@This() = @fieldParentPtr("step", step); + const self: *D8Glob = @fieldParentPtr("step", step); - const d8 = glob.run; + const d8 = self.run; + + // NOTE(jae): 2026-03-01 + // This step uses the output of various Java files from a .zig-cache folder so + // in theory we shouldn't need to handle addDirectoryWatchInput + // + // ie. .zig-cache/o/078c6c3d6e14d425d58e182be74b7006/android_classes + // const need_derived_inputs = try step.addDirectoryWatchInput(self.dir); // Triggers on --watch if a Java file is modified. // For example: ZigSDLActivity.java - if (glob.argv_range) |argv_range| { - try d8.argv.replaceRange(d8.step.owner.allocator, argv_range.start, argv_range.end - argv_range.start, &.{}); + if (self.argv_range) |argv_range| { + try d8.file_inputs.replaceRange(d8.step.owner.allocator, argv_range.file_input_start, argv_range.len, &.{}); + try d8.argv.replaceRange(d8.step.owner.allocator, argv_range.argv_start, argv_range.len, &.{}); } const search_dir = if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 15) - glob.dir.getPath3(b, step) + self.dir.getPath3(b, step) else - try glob.dir.getPath4(b, step); + try self.dir.getPath4(b, step); // NOTE(jae): 2024-09-22 // Change current working directory to where the Java classes are @@ -83,7 +92,7 @@ fn make(step: *Step, options: Build.Step.MakeOptions) !void { // A deeper fix to this problem could be: // - Zip up all the *.class files and just provide that as ONE argument or alternatively // - If "d8" has the ability to pass a file of command line parameters, that would work too but I haven't seen any in the docs - d8.setCwd(glob.dir); + d8.setCwd(self.dir); // NOTE(jae): 2025-07-23 // As of Zig 0.15.0-dev.1092+d772c0627, package_name_path.openDir("") is not possible as it assumes you're appending a sub-path @@ -97,6 +106,7 @@ fn make(step: *Step, options: Build.Step.MakeOptions) !void { dir.close(b.graph.io); var optional_argv_start: ?usize = null; + var optional_file_input_start: ?usize = null; var walker = try dir.walk(arena); defer walker.deinit(); while (if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 15) @@ -104,37 +114,54 @@ fn make(step: *Step, options: Build.Step.MakeOptions) !void { else try walker.next(b.graph.io)) |entry| { - if (entry.kind != .file) { - continue; - } - // NOTE(jae): 2024-10-01 - // Initially ignored classes with alternate API postfixes / etc but - // that did not work with SDL2 so no longer do that. - // - !std.mem.containsAtLeast(u8, entry.basename, 1, "$") and - // - !std.mem.containsAtLeast(u8, entry.basename, 1, "_API") - if (std.mem.endsWith(u8, entry.path, file_ext)) { - const absolute_file_path = try search_dir.root_dir.join(gpa, &.{ search_dir.sub_path, entry.path }); - const relative_to_dir_path = absolute_file_path[search_dir.sub_path.len + 1 ..]; - // NOTE(jae): 2024-09-22 - // We set the current working directory to "glob.Dir" and then make arguments be - // relative to that directory. - // - // This is to avoid the Java error "command line too long" that can occur with d8 - if (optional_argv_start == null) { - optional_argv_start = d8.argv.items.len; - } - d8.addFileInput(LazyPath{ - .cwd_relative = absolute_file_path, - }); - d8.addArg(relative_to_dir_path); + switch (entry.kind) { + .directory => { + // NOTE(jae): 2026-03-01 + // This step uses the output of various Java files from a .zig-cache folder so + // in theory we shouldn't need to handle addDirectoryWatchInput + // + // if (need_derived_inputs) { + // const entry_path = try search_dir.join(arena, entry.path); + // try step.addDirectoryWatchInputFromPath(entry_path); + // } + }, + .file => { + // NOTE(jae): 2024-10-01 + // Initially ignored classes with alternate API postfixes / etc but + // that did not work with SDL2 so no longer do that. + // - !std.mem.containsAtLeast(u8, entry.basename, 1, "$") and + // - !std.mem.containsAtLeast(u8, entry.basename, 1, "_API") + if (std.mem.endsWith(u8, entry.path, file_ext)) { + const absolute_file_path = try search_dir.root_dir.join(gpa, &.{ search_dir.sub_path, entry.path }); + const relative_to_dir_path = absolute_file_path[search_dir.sub_path.len + 1 ..]; + // NOTE(jae): 2024-09-22 + // We set the current working directory to "glob.Dir" and then make arguments be + // relative to that directory. + // + // This is to avoid the Java error "command line too long" that can occur with d8 + if (optional_argv_start == null) { + optional_argv_start = d8.argv.items.len; + optional_file_input_start = d8.file_inputs.items.len; + } + d8.addArg(relative_to_dir_path); + d8.addFileInput(LazyPath{ + .cwd_relative = absolute_file_path, + }); + } + }, + else => continue, } } // Track arguments added to "d8" so that we can remove them if "make" is re-run in --watch mode if (optional_argv_start) |argv_start| { - glob.argv_range = .{ - .start = argv_start, - .end = d8.argv.items.len, + const file_input_start = optional_file_input_start orelse unreachable; + const len: u32 = @intCast(d8.argv.items.len - argv_start); + assert(len == d8.file_inputs.items.len - file_input_start); + self.argv_range = .{ + .argv_start = argv_start, + .file_input_start = file_input_start, + .len = len, }; } } From 7375379270f9c8b7c124ea509c7bcdaac1c3cf54 Mon Sep 17 00:00:00 2001 From: Jae B Date: Sun, 1 Mar 2026 16:05:04 +1100 Subject: [PATCH 4/5] update --- src/androidbuild/d8glob.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/androidbuild/d8glob.zig b/src/androidbuild/d8glob.zig index 89384b7..eb51992 100644 --- a/src/androidbuild/d8glob.zig +++ b/src/androidbuild/d8glob.zig @@ -58,7 +58,6 @@ fn make(step: *Step, options: Build.Step.MakeOptions) !void { _ = options; const b = step.owner; const arena = b.allocator; - const gpa = b.allocator; const self: *D8Glob = @fieldParentPtr("step", step); const d8 = self.run; @@ -132,7 +131,7 @@ fn make(step: *Step, options: Build.Step.MakeOptions) !void { // - !std.mem.containsAtLeast(u8, entry.basename, 1, "$") and // - !std.mem.containsAtLeast(u8, entry.basename, 1, "_API") if (std.mem.endsWith(u8, entry.path, file_ext)) { - const absolute_file_path = try search_dir.root_dir.join(gpa, &.{ search_dir.sub_path, entry.path }); + const absolute_file_path = try search_dir.root_dir.join(arena, &.{ search_dir.sub_path, entry.path }); const relative_to_dir_path = absolute_file_path[search_dir.sub_path.len + 1 ..]; // NOTE(jae): 2024-09-22 // We set the current working directory to "glob.Dir" and then make arguments be From 2ab33cb65fe65d988323e40ff70438d72e6ca77c Mon Sep 17 00:00:00 2001 From: Jae B Date: Sun, 1 Mar 2026 16:17:49 +1100 Subject: [PATCH 5/5] do --- src/androidbuild/DirectoryFileInput.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/androidbuild/DirectoryFileInput.zig b/src/androidbuild/DirectoryFileInput.zig index 995c1ce..08f9357 100644 --- a/src/androidbuild/DirectoryFileInput.zig +++ b/src/androidbuild/DirectoryFileInput.zig @@ -48,7 +48,11 @@ pub fn create(owner: *Build, run: *Run, dir: LazyPath) void { fn make(step: *Step, options: Build.Step.MakeOptions) !void { const b = step.owner; - const gpa = options.gpa; + const gpa = if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 14) + // Deprecated: Zig 0.14.X doesn't have options.gpa + b.allocator + else + options.gpa; const arena = b.allocator; const self: *DirectoryFileInput = @fieldParentPtr("step", step); const run = self.run;