1const Options = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const Configuration = std.Build.Configuration;
6const Cache = std.Build.Cache;
7
8const Step = @import("../Step.zig");
9const Maker = @import("../../Maker.zig");
10
11pub fn make(
12 options: *Options,
13 step_index: Configuration.Step.Index,
14 maker: *Maker,
15 progress_node: std.Progress.Node,
16) Step.ExtendedMakeError!void {
17 _ = options;
18
19 const graph = maker.graph;
20 const step = maker.stepByIndex(step_index);
21 const io = graph.io;
22 const cache_root = graph.local_cache_root;
23 const arena = graph.arena; // TODO don't leak into the process arena
24 const conf = &maker.scanned_config.configuration;
25 const conf_step = step_index.ptr(conf);
26 const conf_options = conf_step.extended.get(conf.extra).options;
27 const contents = conf_options.contents.slice(conf);
28
29 // This step operates under the assumption that all contents of the
30 // generated zig file are observable by dependant steps, as well as the
31 // contents of files added via Options.Arg.
32
33 step.clearWatchInputs(maker);
34
35 var man = graph.cache.obtain();
36 defer man.deinit();
37
38 var args_bytes: std.ArrayList(u8) = .empty;
39
40 for (conf_options.args.slice) |arg| {
41 const name = arg.name.slice(conf);
42 const lazy_path = arg.path.get(conf);
43 try step.addWatchInput(maker, arena, lazy_path);
44 const arg_path = try maker.resolveLazyPath(arena, lazy_path, step_index);
45 _ = try man.addFilePath(arg_path, null);
46 try args_bytes.print(arena, "pub const {f}: []const u8 = \"{f}\";\n", .{
47 std.zig.fmtId(name), arg_path.fmtEscapeString(),
48 });
49 }
50
51 man.hash.addBytes(contents);
52 man.hash.addBytes(args_bytes.items);
53
54 const basename = "options.zig";
55
56 if (try step.cacheHitWatched(maker, &man, progress_node)) {
57 const digest = man.final();
58 maker.generatedPath(conf_options.generated_file).* = .{
59 .root_dir = cache_root,
60 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
61 };
62 step.result_cached = true;
63 return;
64 }
65
66 const digest = man.final();
67 const out_path: Cache.Path = .{
68 .root_dir = cache_root,
69 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
70 };
71
72 var file: Io.File = out_path.root_dir.handle.createFile(io, out_path.sub_path, .{}) catch |err| switch (err) {
73 error.Canceled => |e| return e,
74 error.FileNotFound => f: {
75 out_path.root_dir.handle.createDirPath(io, Io.Dir.path.dirname(out_path.sub_path).?) catch |inner| switch (inner) {
76 error.Canceled => |e| return e,
77 else => |e| return step.fail(maker, "failed to create {f}: {t}", .{ out_path, e }),
78 };
79 break :f out_path.root_dir.handle.createFile(io, out_path.sub_path, .{}) catch |inner| switch (inner) {
80 error.Canceled => |e| return e,
81 else => |e| return step.fail(maker, "failed to create {f}: {t}", .{ out_path, e }),
82 };
83 },
84 else => |e| return step.fail(maker, "failed to create {f}: {t}", .{ out_path, e }),
85 };
86 defer file.close(io);
87
88 // No buffer because we already have all contents buffered.
89 var file_writer = file.writer(io, &.{});
90 var data: [2][]const u8 = .{ contents, args_bytes.items };
91 file_writer.interface.writeVecAll(&data) catch |write_err| switch (write_err) {
92 error.WriteFailed => switch (file_writer.err.?) {
93 error.Canceled => |e| return e,
94 else => |e| return step.fail(maker, "failed to write to {f}: {t}", .{ out_path, e }),
95 },
96 };
97
98 try step.writeManifestAndWatch(maker, &man);
99
100 maker.generatedPath(conf_options.generated_file).* = out_path;
101}