authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-17 14:49:43-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-25 18:54:35-07:00
log43209551b73b670cbb1505fd5fc45980d763aa9c
tree17619c631b68bea487af17b9f04af22c0269146c
parentbd4c1e34d28bb7ab88ada31bb0fa01fda6e4b201

maker: implement Step.Options

also revert #35224

3 files changed, 79 insertions(+), 52 deletions(-)

BRANCH_TODO+3
...@@ -20,6 +20,8 @@...@@ -20,6 +20,8 @@
20* make the generated dependencies.zig be dependencies.zon and don't put absolute paths in there20* make the generated dependencies.zig be dependencies.zon and don't put absolute paths in there
21 - and adjust dependencyInner to not openDir()21 - and adjust dependencyInner to not openDir()
2222
23* re-evaluate https://codeberg.org/ziglang/zig/pulls/35224
24
23## Followup Issues25## Followup Issues
24* stop leaking into global process arena26* stop leaking into global process arena
25* reduce the size of Maker.Step.Extended (make Run smaller) probably by using an arena per make27* reduce the size of Maker.Step.Extended (make Run smaller) probably by using an arena per make
...@@ -32,6 +34,7 @@...@@ -32,6 +34,7 @@
32* fmt step: import zig fmt code directly rather than child proc34* fmt step: import zig fmt code directly rather than child proc
33* UpdateSourceFiles: introduce Group35* UpdateSourceFiles: introduce Group
34* WriteFiles: introduce Group36* WriteFiles: introduce Group
37* re-examine the use case of adding file paths to Options steps
3538
36## Already Filed Followup Issues39## Already Filed Followup Issues
37* build system fmt step with check=false does not acquire a write lock on source files #35204 40* build system fmt step with check=false does not acquire a write lock on source files #35204
lib/compiler/Maker/Step.zig+4-2
...@@ -23,6 +23,7 @@ pub const Fmt = @import("Step/Fmt.zig");...@@ -23,6 +23,7 @@ pub const Fmt = @import("Step/Fmt.zig");
23pub const InstallArtifact = @import("Step/InstallArtifact.zig");23pub const InstallArtifact = @import("Step/InstallArtifact.zig");
24pub const InstallFile = @import("Step/InstallFile.zig");24pub const InstallFile = @import("Step/InstallFile.zig");
25pub const ObjCopy = @import("Step/ObjCopy.zig");25pub const ObjCopy = @import("Step/ObjCopy.zig");
26pub const Options = @import("Step/Options.zig");
26pub const Run = @import("Step/Run.zig");27pub const Run = @import("Step/Run.zig");
27pub const UpdateSourceFiles = @import("Step/UpdateSourceFiles.zig");28pub const UpdateSourceFiles = @import("Step/UpdateSourceFiles.zig");
2829
...@@ -79,7 +80,7 @@ pub const Extended = union(enum) {...@@ -79,7 +80,7 @@ pub const Extended = union(enum) {
79 install_dir: Todo,80 install_dir: Todo,
80 install_file: InstallFile,81 install_file: InstallFile,
81 obj_copy: ObjCopy,82 obj_copy: ObjCopy,
82 options: Todo,83 options: Options,
83 remove_dir: Todo,84 remove_dir: Todo,
84 run: Run,85 run: Run,
85 top_level: TopLevel,86 top_level: TopLevel,
...@@ -321,7 +322,8 @@ pub fn reset(step: *Step, maker: *Maker) void {...@@ -321,7 +322,8 @@ pub fn reset(step: *Step, maker: *Maker) void {
321 step.result_peak_rss = 0;322 step.result_peak_rss = 0;
322 step.result_failed_command = null;323 step.result_failed_command = null;
323 step.test_results = .{};324 step.test_results = .{};
324 step.clearWatchInputs(maker);325 // We do not clearWatchInputs here because each step manages that choice
326 // independently.
325327
326 step.result_error_bundle.deinit(gpa);328 step.result_error_bundle.deinit(gpa);
327 step.result_error_bundle = std.zig.ErrorBundle.empty;329 step.result_error_bundle = std.zig.ErrorBundle.empty;
lib/compiler/Maker/Step/Options.zig+72-50
...@@ -1,7 +1,9 @@...@@ -1,7 +1,9 @@
1const Options = @This();1const Options = @This();
22
3const std = @import("std");3const std = @import("std");
4const Io = std.Io;
4const Configuration = std.Build.Configuration;5const Configuration = std.Build.Configuration;
6const Cache = std.Build.Cache;
57
6const Step = @import("../Step.zig");8const Step = @import("../Step.zig");
7const Maker = @import("../../Maker.zig");9const Maker = @import("../../Maker.zig");
...@@ -12,6 +14,8 @@ pub fn make(...@@ -12,6 +14,8 @@ pub fn make(
12 maker: *Maker,14 maker: *Maker,
13 progress_node: std.Progress.Node,15 progress_node: std.Progress.Node,
14) Step.ExtendedMakeError!void {16) Step.ExtendedMakeError!void {
17 _ = options;
18
15 // This step completes so quickly that no progress reporting is necessary.19 // This step completes so quickly that no progress reporting is necessary.
16 _ = progress_node;20 _ = progress_node;
1721
...@@ -19,64 +23,82 @@ pub fn make(...@@ -19,64 +23,82 @@ pub fn make(
19 const step = maker.stepByIndex(step_index);23 const step = maker.stepByIndex(step_index);
20 const io = graph.io;24 const io = graph.io;
21 const cache_root = graph.local_cache_root;25 const cache_root = graph.local_cache_root;
26 const arena = graph.arena; // TODO don't leak into the process arena
27 const conf = &maker.scanned_config.configuration;
28 const conf_step = step_index.ptr(conf);
29 const conf_options = conf_step.extended.get(conf.extra).options;
30 const contents = conf_options.contents.slice(conf);
2231
23 for (options.args.items) |arg| {32 // This step operates under the assumption that all contents of the
24 options.addOption(33 // generated zig file are observable by dependant steps, as well as the
25 []const u8,34 // contents of files added via Options.Arg.
26 arg.name,
27 arg.path.getPath2(b, step),
28 );
29 }
30 if (!step.inputs.populated()) for (options.args.items) |arg| {
31 try step.addWatchInput(arg.path);
32 };
3335
34 const basename = "options.zig";36 step.clearWatchInputs(maker);
37
38 var man = graph.cache.obtain();
39 defer man.deinit();
3540
36 // Hash contents to file name.41 var args_bytes: std.ArrayList(u8) = .empty;
37 var hash = graph.cache.hash;
38 // Random bytes to make unique. Refresh this with new random bytes when
39 // implementation is modified in a non-backwards-compatible way.
40 hash.add(@as(u32, 0xad95e922));
41 hash.addBytes(options.contents.items);
42 const sub_path = "c" ++ fs.path.sep_str ++ hash.final() ++ fs.path.sep_str ++ basename;
4342
44 options.generated_file.path = try cache_root.join(arena, &.{sub_path});43 for (conf_options.args.slice) |arg| {
44 const name = arg.name.slice(conf);
45 const lazy_path = arg.path.get(conf);
46 try step.addWatchInput(maker, arena, lazy_path);
47 const arg_path = try maker.resolveLazyPath(arena, lazy_path, step_index);
48 _ = try man.addFilePath(arg_path, null);
49 try args_bytes.print(arena, "pub const {f}: []const u8 = \"{f}\";\n", .{
50 std.zig.fmtId(name), arg_path.fmtEscapeString(),
51 });
52 }
4553
46 // Optimize for the hot path. Stat the file, and if it already exists,54 man.hash.addBytes(contents);
47 // cache hit.55 man.hash.addBytes(args_bytes.items);
48 if (cache_root.handle.access(io, sub_path, .{})) |_| {56
49 // This is the hot path, success.57 const basename = "options.zig";
58
59 if (try step.cacheHitAndWatch(maker, &man)) {
60 const digest = man.final();
61 maker.generatedPath(conf_options.generated_file).* = .{
62 .root_dir = cache_root,
63 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
64 };
50 step.result_cached = true;65 step.result_cached = true;
51 return;66 return;
52 } else |outer_err| switch (outer_err) {67 }
53 error.FileNotFound => {
54 var atomic_file = cache_root.handle.createFileAtomic(io, sub_path, .{
55 .replace = false,
56 .make_path = true,
57 }) catch |err| return step.fail("failed to create temporary path for '{f}{s}': {t}", .{
58 cache_root, sub_path, err,
59 });
60 defer atomic_file.deinit(io);
61
62 atomic_file.file.writeStreamingAll(io, options.contents.items) catch |err| {
63 return step.fail("failed to write options to temporary path for '{f}{s}': {t}", .{
64 cache_root, sub_path, err,
65 });
66 };
6768
68 atomic_file.link(io) catch |err| switch (err) {69 const digest = man.final();
69 error.PathAlreadyExists => {70 const out_path: Cache.Path = .{
70 step.result_cached = true;71 .root_dir = cache_root,
71 return;72 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
72 },73 };
73 else => return step.fail("failed to link temporary file into '{f}{s}': {t}", .{74
74 cache_root, sub_path, err,75 var file: Io.File = out_path.root_dir.handle.createFile(io, out_path.sub_path, .{}) catch |err| switch (err) {
75 }),76 error.Canceled => |e| return e,
77 error.FileNotFound => f: {
78 out_path.root_dir.handle.createDirPath(io, Io.Dir.path.dirname(out_path.sub_path).?) catch |inner| switch (inner) {
79 error.Canceled => |e| return e,
80 else => |e| return step.fail(maker, "failed to create {f}: {t}", .{ out_path, e }),
81 };
82 break :f out_path.root_dir.handle.createFile(io, out_path.sub_path, .{}) catch |inner| switch (inner) {
83 error.Canceled => |e| return e,
84 else => |e| return step.fail(maker, "failed to create {f}: {t}", .{ out_path, e }),
76 };85 };
77 },86 },
78 else => |e| return step.fail("unable to access options file '{f}{s}': {t}", .{87 else => |e| return step.fail(maker, "failed to create {f}: {t}", .{ out_path, e }),
79 cache_root, sub_path, e,88 };
80 }),89 defer file.close(io);
81 }90
91 // No buffer because we already have all contents buffered.
92 var file_writer = file.writer(io, &.{});
93 var data: [2][]const u8 = .{ contents, args_bytes.items };
94 file_writer.interface.writeVecAll(&data) catch |write_err| switch (write_err) {
95 error.WriteFailed => switch (file_writer.err.?) {
96 error.Canceled => |e| return e,
97 else => |e| return step.fail(maker, "failed to write to {f}: {t}", .{ out_path, e }),
98 },
99 };
100
101 try step.writeManifestAndWatch(maker, &man);
102
103 maker.generatedPath(conf_options.generated_file).* = out_path;
82}104}