Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 73 additions & 14 deletions src/androidbuild/DirectoryFileInput.zig
Original file line number Diff line number Diff line change
@@ -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,

Expand All @@ -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(.{
Expand All @@ -31,19 +38,47 @@ 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);
// If dir is generated then this will wait for that dir to generate
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 = 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;

// 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
Expand All @@ -60,19 +95,43 @@ 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)
try walker.next()
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),
};
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/androidbuild/apk.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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();
};

Expand Down
30 changes: 22 additions & 8 deletions src/androidbuild/builtin_options_update.zig
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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(.{
Expand All @@ -34,13 +35,26 @@ 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 {
const b = step.owner;
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
Expand All @@ -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);
Expand Down
98 changes: 62 additions & 36 deletions src/androidbuild/d8glob.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -57,21 +58,28 @@ 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 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
Expand All @@ -83,7 +91,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
Expand All @@ -97,44 +105,62 @@ 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)
try walker.next()
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(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
// 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,
};
}
}
Expand Down