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 @@
2020* make the generated dependencies.zig be dependencies.zon and don't put absolute paths in there
2121 - and adjust dependencyInner to not openDir()
2222
23* re-evaluate https://codeberg.org/ziglang/zig/pulls/35224
24
2325## Followup Issues
2426* stop leaking into global process arena
2527* reduce the size of Maker.Step.Extended (make Run smaller) probably by using an arena per make
......@@ -32,6 +34,7 @@
3234* fmt step: import zig fmt code directly rather than child proc
3335* UpdateSourceFiles: introduce Group
3436* WriteFiles: introduce Group
37* re-examine the use case of adding file paths to Options steps
3538
3639## Already Filed Followup Issues
3740* 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");
2323pub const InstallArtifact = @import("Step/InstallArtifact.zig");
2424pub const InstallFile = @import("Step/InstallFile.zig");
2525pub const ObjCopy = @import("Step/ObjCopy.zig");
26pub const Options = @import("Step/Options.zig");
2627pub const Run = @import("Step/Run.zig");
2728pub const UpdateSourceFiles = @import("Step/UpdateSourceFiles.zig");
2829
......@@ -79,7 +80,7 @@ pub const Extended = union(enum) {
7980 install_dir: Todo,
8081 install_file: InstallFile,
8182 obj_copy: ObjCopy,
82 options: Todo,
83 options: Options,
8384 remove_dir: Todo,
8485 run: Run,
8586 top_level: TopLevel,
......@@ -321,7 +322,8 @@ pub fn reset(step: *Step, maker: *Maker) void {
321322 step.result_peak_rss = 0;
322323 step.result_failed_command = null;
323324 step.test_results = .{};
324 step.clearWatchInputs(maker);
325 // We do not clearWatchInputs here because each step manages that choice
326 // independently.
325327
326328 step.result_error_bundle.deinit(gpa);
327329 step.result_error_bundle = std.zig.ErrorBundle.empty;
lib/compiler/Maker/Step/Options.zig+72-50
......@@ -1,7 +1,9 @@
11const Options = @This();
22
33const std = @import("std");
4const Io = std.Io;
45const Configuration = std.Build.Configuration;
6const Cache = std.Build.Cache;
57
68const Step = @import("../Step.zig");
79const Maker = @import("../../Maker.zig");
......@@ -12,6 +14,8 @@ pub fn make(
1214 maker: *Maker,
1315 progress_node: std.Progress.Node,
1416) Step.ExtendedMakeError!void {
17 _ = options;
18
1519 // This step completes so quickly that no progress reporting is necessary.
1620 _ = progress_node;
1721
......@@ -19,64 +23,82 @@ pub fn make(
1923 const step = maker.stepByIndex(step_index);
2024 const io = graph.io;
2125 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| {
24 options.addOption(
25 []const u8,
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 };
32 // This step operates under the assumption that all contents of the
33 // generated zig file are observable by dependant steps, as well as the
34 // contents of files added via Options.Arg.
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.
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;
41 var args_bytes: std.ArrayList(u8) = .empty;
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,
47 // cache hit.
48 if (cache_root.handle.access(io, sub_path, .{})) |_| {
49 // This is the hot path, success.
54 man.hash.addBytes(contents);
55 man.hash.addBytes(args_bytes.items);
56
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 };
5065 step.result_cached = true;
5166 return;
52 } else |outer_err| switch (outer_err) {
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 };
67 }
6768
68 atomic_file.link(io) catch |err| switch (err) {
69 error.PathAlreadyExists => {
70 step.result_cached = true;
71 return;
72 },
73 else => return step.fail("failed to link temporary file into '{f}{s}': {t}", .{
74 cache_root, sub_path, err,
75 }),
69 const digest = man.final();
70 const out_path: Cache.Path = .{
71 .root_dir = cache_root,
72 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
73 };
74
75 var file: Io.File = out_path.root_dir.handle.createFile(io, out_path.sub_path, .{}) catch |err| switch (err) {
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 }),
7685 };
7786 },
78 else => |e| return step.fail("unable to access options file '{f}{s}': {t}", .{
79 cache_root, sub_path, e,
80 }),
81 }
87 else => |e| return step.fail(maker, "failed to create {f}: {t}", .{ out_path, e }),
88 };
89 defer file.close(io);
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;
82104}