| ... | @@ -1,7 +1,9 @@ | ... | @@ -1,7 +1,9 @@ |
| 1 | const Options = @This(); | 1 | const Options = @This(); |
| 2 | | 2 | |
| 3 | const std = @import("std"); | 3 | const std = @import("std"); |
| | 4 | const Io = std.Io; |
| 4 | const Configuration = std.Build.Configuration; | 5 | const Configuration = std.Build.Configuration; |
| | 6 | const Cache = std.Build.Cache; |
| 5 | | 7 | |
| 6 | const Step = @import("../Step.zig"); | 8 | const Step = @import("../Step.zig"); |
| 7 | const Maker = @import("../../Maker.zig"); | 9 | const 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; |
| 17 | | 21 | |
| ... | @@ -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); |
| 22 | | 31 | |
| 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 | }; | | |
| 33 | | 35 | |
| 34 | const basename = "options.zig"; | 36 | step.clearWatchInputs(maker); |
| | 37 | |
| | 38 | var man = graph.cache.obtain(); |
| | 39 | defer man.deinit(); |
| 35 | | 40 | |
| 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; | | |
| 43 | | 42 | |
| 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 | } |
| 45 | | 53 | |
| 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 | }; | | |
| 67 | | 68 | |
| 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 | } |