commit 43209551b73b670cbb1505fd5fc45980d763aa9c (tree)
parent bd4c1e34d28bb7ab88ada31bb0fa01fda6e4b201
Author: Andrew Kelley <andrew@ziglang.org>
Date: Sun, 17 May 2026 14:49:43 -0700
maker: implement Step.Options
also revert #35224
Diffstat:
3 files changed, 79 insertions(+), 52 deletions(-)
diff --git a/BRANCH_TODO b/BRANCH_TODO
@@ -20,6 +20,8 @@
* make the generated dependencies.zig be dependencies.zon and don't put absolute paths in there
- and adjust dependencyInner to not openDir()
+* re-evaluate https://codeberg.org/ziglang/zig/pulls/35224
+
## Followup Issues
* stop leaking into global process arena
* reduce the size of Maker.Step.Extended (make Run smaller) probably by using an arena per make
@@ -32,6 +34,7 @@
* fmt step: import zig fmt code directly rather than child proc
* UpdateSourceFiles: introduce Group
* WriteFiles: introduce Group
+* re-examine the use case of adding file paths to Options steps
## Already Filed Followup Issues
* build system fmt step with check=false does not acquire a write lock on source files #35204
diff --git a/lib/compiler/Maker/Step.zig b/lib/compiler/Maker/Step.zig
@@ -23,6 +23,7 @@ pub const Fmt = @import("Step/Fmt.zig");
pub const InstallArtifact = @import("Step/InstallArtifact.zig");
pub const InstallFile = @import("Step/InstallFile.zig");
pub const ObjCopy = @import("Step/ObjCopy.zig");
+pub const Options = @import("Step/Options.zig");
pub const Run = @import("Step/Run.zig");
pub const UpdateSourceFiles = @import("Step/UpdateSourceFiles.zig");
@@ -79,7 +80,7 @@ pub const Extended = union(enum) {
install_dir: Todo,
install_file: InstallFile,
obj_copy: ObjCopy,
- options: Todo,
+ options: Options,
remove_dir: Todo,
run: Run,
top_level: TopLevel,
@@ -321,7 +322,8 @@ pub fn reset(step: *Step, maker: *Maker) void {
step.result_peak_rss = 0;
step.result_failed_command = null;
step.test_results = .{};
- step.clearWatchInputs(maker);
+ // We do not clearWatchInputs here because each step manages that choice
+ // independently.
step.result_error_bundle.deinit(gpa);
step.result_error_bundle = std.zig.ErrorBundle.empty;
diff --git a/lib/compiler/Maker/Step/Options.zig b/lib/compiler/Maker/Step/Options.zig
@@ -1,7 +1,9 @@
const Options = @This();
const std = @import("std");
+const Io = std.Io;
const Configuration = std.Build.Configuration;
+const Cache = std.Build.Cache;
const Step = @import("../Step.zig");
const Maker = @import("../../Maker.zig");
@@ -12,6 +14,8 @@ pub fn make(
maker: *Maker,
progress_node: std.Progress.Node,
) Step.ExtendedMakeError!void {
+ _ = options;
+
// This step completes so quickly that no progress reporting is necessary.
_ = progress_node;
@@ -19,64 +23,82 @@ pub fn make(
const step = maker.stepByIndex(step_index);
const io = graph.io;
const cache_root = graph.local_cache_root;
+ const arena = graph.arena; // TODO don't leak into the process arena
+ const conf = &maker.scanned_config.configuration;
+ const conf_step = step_index.ptr(conf);
+ const conf_options = conf_step.extended.get(conf.extra).options;
+ const contents = conf_options.contents.slice(conf);
- for (options.args.items) |arg| {
- options.addOption(
- []const u8,
- arg.name,
- arg.path.getPath2(b, step),
- );
- }
- if (!step.inputs.populated()) for (options.args.items) |arg| {
- try step.addWatchInput(arg.path);
- };
+ // This step operates under the assumption that all contents of the
+ // generated zig file are observable by dependant steps, as well as the
+ // contents of files added via Options.Arg.
- const basename = "options.zig";
+ step.clearWatchInputs(maker);
+
+ var man = graph.cache.obtain();
+ defer man.deinit();
- // Hash contents to file name.
- var hash = graph.cache.hash;
- // Random bytes to make unique. Refresh this with new random bytes when
- // implementation is modified in a non-backwards-compatible way.
- hash.add(@as(u32, 0xad95e922));
- hash.addBytes(options.contents.items);
- const sub_path = "c" ++ fs.path.sep_str ++ hash.final() ++ fs.path.sep_str ++ basename;
+ var args_bytes: std.ArrayList(u8) = .empty;
- options.generated_file.path = try cache_root.join(arena, &.{sub_path});
+ for (conf_options.args.slice) |arg| {
+ const name = arg.name.slice(conf);
+ const lazy_path = arg.path.get(conf);
+ try step.addWatchInput(maker, arena, lazy_path);
+ const arg_path = try maker.resolveLazyPath(arena, lazy_path, step_index);
+ _ = try man.addFilePath(arg_path, null);
+ try args_bytes.print(arena, "pub const {f}: []const u8 = \"{f}\";\n", .{
+ std.zig.fmtId(name), arg_path.fmtEscapeString(),
+ });
+ }
- // Optimize for the hot path. Stat the file, and if it already exists,
- // cache hit.
- if (cache_root.handle.access(io, sub_path, .{})) |_| {
- // This is the hot path, success.
+ man.hash.addBytes(contents);
+ man.hash.addBytes(args_bytes.items);
+
+ const basename = "options.zig";
+
+ if (try step.cacheHitAndWatch(maker, &man)) {
+ const digest = man.final();
+ maker.generatedPath(conf_options.generated_file).* = .{
+ .root_dir = cache_root,
+ .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
+ };
step.result_cached = true;
return;
- } else |outer_err| switch (outer_err) {
- error.FileNotFound => {
- var atomic_file = cache_root.handle.createFileAtomic(io, sub_path, .{
- .replace = false,
- .make_path = true,
- }) catch |err| return step.fail("failed to create temporary path for '{f}{s}': {t}", .{
- cache_root, sub_path, err,
- });
- defer atomic_file.deinit(io);
-
- atomic_file.file.writeStreamingAll(io, options.contents.items) catch |err| {
- return step.fail("failed to write options to temporary path for '{f}{s}': {t}", .{
- cache_root, sub_path, err,
- });
- };
+ }
- atomic_file.link(io) catch |err| switch (err) {
- error.PathAlreadyExists => {
- step.result_cached = true;
- return;
- },
- else => return step.fail("failed to link temporary file into '{f}{s}': {t}", .{
- cache_root, sub_path, err,
- }),
+ const digest = man.final();
+ const out_path: Cache.Path = .{
+ .root_dir = cache_root,
+ .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
+ };
+
+ var file: Io.File = out_path.root_dir.handle.createFile(io, out_path.sub_path, .{}) catch |err| switch (err) {
+ error.Canceled => |e| return e,
+ error.FileNotFound => f: {
+ out_path.root_dir.handle.createDirPath(io, Io.Dir.path.dirname(out_path.sub_path).?) catch |inner| switch (inner) {
+ error.Canceled => |e| return e,
+ else => |e| return step.fail(maker, "failed to create {f}: {t}", .{ out_path, e }),
+ };
+ break :f out_path.root_dir.handle.createFile(io, out_path.sub_path, .{}) catch |inner| switch (inner) {
+ error.Canceled => |e| return e,
+ else => |e| return step.fail(maker, "failed to create {f}: {t}", .{ out_path, e }),
};
},
- else => |e| return step.fail("unable to access options file '{f}{s}': {t}", .{
- cache_root, sub_path, e,
- }),
- }
+ else => |e| return step.fail(maker, "failed to create {f}: {t}", .{ out_path, e }),
+ };
+ defer file.close(io);
+
+ // No buffer because we already have all contents buffered.
+ var file_writer = file.writer(io, &.{});
+ var data: [2][]const u8 = .{ contents, args_bytes.items };
+ file_writer.interface.writeVecAll(&data) catch |write_err| switch (write_err) {
+ error.WriteFailed => switch (file_writer.err.?) {
+ error.Canceled => |e| return e,
+ else => |e| return step.fail(maker, "failed to write to {f}: {t}", .{ out_path, e }),
+ },
+ };
+
+ try step.writeManifestAndWatch(maker, &man);
+
+ maker.generatedPath(conf_options.generated_file).* = out_path;
}