From f9c4b5cdcedf68c1256b32cf438f6abb2725d820 Mon Sep 17 00:00:00 2001 From: Jae B Date: Sun, 1 Mar 2026 13:09:15 +1100 Subject: [PATCH] fix d8 step to work with "zig build --watch" when modifying Java files --- .gitignore | 1 + examples/sdl2/third-party/sdl2/build.zig.zon | 3 +- src/androidbuild/apk.zig | 89 +++++++++++--------- src/androidbuild/d8glob.zig | 42 ++++++++- 4 files changed, 90 insertions(+), 45 deletions(-) diff --git a/.gitignore b/.gitignore index d8c8979..36addef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .zig-cache zig-out +zig-pkg diff --git a/examples/sdl2/third-party/sdl2/build.zig.zon b/examples/sdl2/third-party/sdl2/build.zig.zon index 7300517..05e869a 100644 --- a/examples/sdl2/third-party/sdl2/build.zig.zon +++ b/examples/sdl2/third-party/sdl2/build.zig.zon @@ -1,5 +1,5 @@ .{ - .name = "sdl", + .name = .sdl, .version = "2.32.10", .dependencies = .{ .sdl2 = .{ @@ -14,4 +14,5 @@ "build.zig.zon", "src", }, + .fingerprint = 0xec638ccb4e5e0ae0, } diff --git a/src/androidbuild/apk.zig b/src/androidbuild/apk.zig index b53c03a..3728cca 100644 --- a/src/androidbuild/apk.zig +++ b/src/androidbuild/apk.zig @@ -15,6 +15,7 @@ const getAndroidTriple = androidbuild.getAndroidTriple; const runNameContext = androidbuild.runNameContext; const printErrorsAndExit = androidbuild.printErrorsAndExit; +const Allocator = std.mem.Allocator; const Target = std.Target; const Step = std.Build.Step; const ResolvedTarget = std.Build.ResolvedTarget; @@ -219,7 +220,7 @@ pub fn addInstallApk(apk: *Apk) *Step.InstallFile { }; } -fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { +fn doInstallApk(apk: *Apk) Allocator.Error!*Step.InstallFile { const b = apk.b; const key_store: KeyStore = apk.key_store orelse .empty; @@ -299,12 +300,14 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { }; // ie. "$ANDROID_HOME/Sdk/platforms/android-{api_level}/android.jar" - const root_jar = b.pathResolve(&[_][]const u8{ - apk.sdk.android_sdk_path, - "platforms", - b.fmt("android-{d}", .{@intFromEnum(apk.api_level)}), - "android.jar", - }); + const root_jar: LazyPath = .{ + .cwd_relative = b.pathResolve(&[_][]const u8{ + apk.sdk.android_sdk_path, + "platforms", + b.fmt("android-{d}", .{@intFromEnum(apk.api_level)}), + "android.jar", + }), + }; // Make resources.apk from: // - resources.flat.zip (created from "aapt2 compile") @@ -319,11 +322,13 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { const aapt2link = b.addSystemCommand(&[_][]const u8{ apk.build_tools.aapt2, "link", - "-I", // add an existing package to base include set - root_jar, }); aapt2link.setName(runNameContext("aapt2 link")); + // Add '-I android_sdk/platforms/android_version/android.jar' + aapt2link.addArg("-I"); + aapt2link.addFileArg(root_jar); + if (b.verbose) { aapt2link.addArg("-v"); } @@ -552,13 +557,20 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { // Source: https://github.com/libsdl-org/SDL/blob/release-2.30.7/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java#L2045 "-encoding", "utf8", - "-cp", - root_jar, - // NOTE(jae): 2024-09-19 - // Debug issues with the SDL.java classes - // "-Xlint:deprecation", }); javac_cmd.setName(runNameContext("javac")); + + // Add root jar + 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"); + } + + // Output directory javac_cmd.addArg("-d"); const java_classes_output_dir = javac_cmd.addOutputDirectoryArg("android_classes"); @@ -574,23 +586,8 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { }); d8.setName(runNameContext("d8")); - // Prepend JDK bin path so d8 can always find "java", etc - if (apk.sdk.jdk_path.len > 0) { - var env_map = d8.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, - }); - try env_map.put("PATH", new_path); - } - - // ie. android_sdk/platforms/android-{api-level}/android.jar - d8.addArg("--lib"); - d8.addArg(root_jar); - - d8.addArg("--output"); - const dex_output_dir = d8.addOutputDirectoryArg("android_dex"); + // Add JDK bin path so d8 can always find "java", etc + try apk.updatePathWithJdk(d8); // NOTE(jae): 2024-09-22 // As per documentation for d8, we may want to specific the minimum API level we want @@ -599,7 +596,14 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { // d8.addArg(number_as_string); // add each output *.class file - D8Glob.create(b, d8, java_classes_output_dir); + D8Glob.create(b, d8, java_classes_output_dir, root_jar); + + // ie. android_sdk/platforms/android-{api-level}/android.jar + d8.addArg("--lib"); + d8.addFileArg(root_jar); + + d8.addArg("--output"); + const dex_output_dir = d8.addOutputDirectoryArg("android_dex"); const dex_file = dex_output_dir.path(b, "classes.dex"); // Append classes.dex to apk @@ -949,18 +953,23 @@ fn updateSharedLibraryOptions(artifact: *std.Build.Step.Compile) void { } /// Prepend JDK bin path so "d8", "apksigner", etc can always find "java" -fn updatePathWithJdk(apk: *Apk, run: *std.Build.Step.Run) !void { +fn updatePathWithJdk(apk: *Apk, run: *std.Build.Step.Run) Allocator.Error!void { if (apk.sdk.jdk_path.len == 0) return; const b = apk.b; - 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, - }); - try env_map.put("PATH", new_path); + if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 15) { + // Deprecated path: Add "java" path to env + 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, + }); + try env_map.put("PATH", new_path); + } else { + run.addPathDir(b.pathJoin(&.{ apk.sdk.jdk_path, "bin" })); + } } const Apk = @This(); diff --git a/src/androidbuild/d8glob.zig b/src/androidbuild/d8glob.zig index ed04767..f9a2c1a 100644 --- a/src/androidbuild/d8glob.zig +++ b/src/androidbuild/d8glob.zig @@ -21,10 +21,18 @@ run: *Build.Step.Run, /// The directory that will contain the files to glob dir: LazyPath, +/// Track the files added to the Run step for --watch +argv_range: ?ArgRange, + const file_ext = ".class"; +const ArgRange = struct { + start: usize, + end: usize, +}; + /// Creates a D8Glob step which is used to collect all *.class output files after a javac process generates them -pub fn create(owner: *std.Build, run: *Run, dir: LazyPath) void { +pub fn create(owner: *std.Build, run: *Run, dir: LazyPath, root_jar: LazyPath) void { const glob = owner.allocator.create(@This()) catch @panic("OOM"); glob.* = .{ .step = Step.init(.{ @@ -35,19 +43,31 @@ pub fn create(owner: *std.Build, run: *Run, dir: LazyPath) void { }), .run = run, .dir = dir, + .argv_range = null, }; // Run step relies on this finishing run.step.dependOn(&glob.step); // If dir is generated then this will wait for that dir to generate dir.addStepDependencies(&glob.step); + // If root_jar changes, then this should trigger + root_jar.addStepDependencies(&glob.step); } -fn make(step: *Step, _: Build.Step.MakeOptions) !void { +fn make(step: *Step, options: Build.Step.MakeOptions) !void { + _ = options; const b = step.owner; const arena = b.allocator; + const gpa = b.allocator; const glob: *@This() = @fieldParentPtr("step", step); + const d8 = glob.run; + // 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, &.{}); + } + const search_dir = if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 15) glob.dir.getPath3(b, step) else @@ -76,6 +96,7 @@ fn make(step: *Step, _: Build.Step.MakeOptions) !void { else dir.close(b.graph.io); + var optional_argv_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) @@ -92,17 +113,30 @@ fn make(step: *Step, _: 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 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 - d8.addArg(entry.path); + if (optional_argv_start == null) { + optional_argv_start = d8.argv.items.len; + } d8.addFileInput(LazyPath{ - .cwd_relative = try search_dir.root_dir.join(b.allocator, &.{ search_dir.sub_path, entry.path }), + .cwd_relative = absolute_file_path, }); + d8.addArg(relative_to_dir_path); } } + + // 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 D8Glob = @This();