| ... | ... | @@ -1,7 +1,9 @@ |
| 1 | 1 | const Options = @This(); |
| 2 | 2 | |
| 3 | 3 | const std = @import("std"); |
| 4 | const Io = std.Io; |
| 4 | 5 | const Configuration = std.Build.Configuration; |
| 6 | const Cache = std.Build.Cache; |
| 5 | 7 | |
| 6 | 8 | const Step = @import("../Step.zig"); |
| 7 | 9 | const Maker = @import("../../Maker.zig"); |
| ... | ... | @@ -12,6 +14,8 @@ pub fn make( |
| 12 | 14 | maker: *Maker, |
| 13 | 15 | progress_node: std.Progress.Node, |
| 14 | 16 | ) Step.ExtendedMakeError!void { |
| 17 | _ = options; |
| 18 | |
| 15 | 19 | // This step completes so quickly that no progress reporting is necessary. |
| 16 | 20 | _ = progress_node; |
| 17 | 21 | |
| ... | ... | @@ -19,64 +23,82 @@ pub fn make( |
| 19 | 23 | const step = maker.stepByIndex(step_index); |
| 20 | 24 | const io = graph.io; |
| 21 | 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| { |
| 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. |
| 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. |
| 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; |
| 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, |
| 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 | }; |
| 50 | 65 | step.result_cached = true; |
| 51 | 66 | 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 | } |
| 67 | 68 | |
| 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 }), |
| 76 | 85 | }; |
| 77 | 86 | }, |
| 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; |
| 82 | 104 | } |