authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-17 12:14:05+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-17 12:14:05+02:00
logb0e7ebc24fdbd17f01180450c0e12f6187ef3f27
tree4831e5bc73455a6e6e6cac4f799a9fd5f9992e94
parentb7104c95f249e42d0068bc2a94f7f426c0c740f3
parent56571a70d99a896777146e9588d9f6a04124c9a5

Merge pull request 'std.Build.Cache: add is_directory and metadata_only bits; binary manifest format' (#36832) from std.Build.Cache-rework into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/36832

40 files changed, 2744 insertions(+), 1537 deletions(-)

build.zig+15-11
......@@ -263,8 +263,8 @@ pub fn build(b: *std.Build) !void {
263263 const opt_version_string = b.option([]const u8, "version-string", "Override Zig version string. Default is to find out with git.");
264264 const version_slice = if (opt_version_string) |version| version else v: {
265265 if (!std.process.can_spawn) {
266 std.debug.print("error: version info cannot be retrieved from git. Zig version must be provided using -Dversion-string\n", .{});
267 std.process.exit(1);
266 std.log.info("version info can be provided explicitly via \"-Dversion-string\"", .{});
267 std.process.fatal("version info cannot be retrieved from git", .{});
268268 }
269269
270270 // Ensure git version changes get picked up.
......@@ -272,12 +272,13 @@ pub fn build(b: *std.Build) !void {
272272 const io = b.graph.io;
273273 const git_file = b.root.openFile(io, ".git", .{ .allow_directory = false }) catch |err| switch (err) {
274274 error.IsDir => {
275 b.dependOnFileContents(b.path(".git/logs/HEAD"));
275 b.dependOnFileMetadata(b.path(".git/logs/HEAD"));
276276 break :git;
277277 },
278278 else => |e| return e,
279279 };
280280 defer git_file.close(io);
281 b.dependOnFileContents(b.path(".git"));
281282 var line_buffer: ["gitdir: ".len + std.Io.Dir.max_path_bytes + 1]u8 = undefined;
282283 var git_file_reader = git_file.reader(io, &line_buffer);
283284 if (std.mem.cutPrefix(u8, std.mem.trimEnd(u8, try git_file_reader.interface.allocRemaining(
......@@ -285,7 +286,7 @@ pub fn build(b: *std.Build) !void {
285286 .limited("gitdir: ".len + std.Io.Dir.max_path_bytes + "\r\n".len),
286287 ), "\r\n"), "gitdir: ")) |git_dir| {
287288 const head_file = b.pathJoin(&.{ git_dir, "logs", "HEAD" });
288 b.dependOnFileContents(if (std.Io.Dir.path.isAbsolute(head_file))
289 b.dependOnFileMetadata(if (std.Io.Dir.path.isAbsolute(head_file))
289290 b.graph.cwdRelativePath(head_file)
290291 else
291292 b.path(head_file));
......@@ -310,8 +311,9 @@ pub fn build(b: *std.Build) !void {
310311 0 => {
311312 // Tagged release version (e.g. 0.10.0).
312313 if (!mem.eql(u8, git_describe, version_string)) {
313 std.debug.print("Zig version '{s}' does not match Git tag '{s}'\n", .{ version_string, git_describe });
314 std.process.exit(1);
314 std.process.fatal("zig version {q} does not match Git tag {q}", .{
315 version_string, git_describe,
316 });
315317 }
316318 break :v version_string;
317319 },
......@@ -324,13 +326,14 @@ pub fn build(b: *std.Build) !void {
324326
325327 const ancestor_ver = try std.SemanticVersion.parse(tagged_ancestor);
326328 if (zig_version.order(ancestor_ver) != .gt) {
327 std.debug.print("Zig version '{f}' must be greater than tagged ancestor '{f}'\n", .{ zig_version, ancestor_ver });
328 std.process.exit(1);
329 std.process.fatal("zig version {f} must be greater than tagged ancestor {qf}", .{
330 zig_version, ancestor_ver,
331 });
329332 }
330333
331334 // Check that the commit hash is prefixed with a 'g' (a Git convention).
332335 if (commit_id.len < 1 or commit_id[0] != 'g') {
333 std.debug.print("Unexpected `git describe` output: {s}\n", .{git_describe});
336 std.log.warn("unexpected \"git describe\" output: {s}", .{git_describe});
334337 break :v version_string;
335338 }
336339
......@@ -338,7 +341,7 @@ pub fn build(b: *std.Build) !void {
338341 break :v b.fmt("{s}-dev.{s}+{s}", .{ version_string, commit_height, commit_id[1..] });
339342 },
340343 else => {
341 std.debug.print("Unexpected `git describe` output: {s}\n", .{git_describe});
344 std.log.warn("unexpected \"git describe\" output: {s}", .{git_describe});
342345 break :v version_string;
343346 },
344347 }
......@@ -354,7 +357,8 @@ pub fn build(b: *std.Build) !void {
354357 const file_contents = cwd.readFileAlloc(io, config_h_path, arena, .limited(max_config_h_bytes)) catch unreachable;
355358 break :blk parseConfigH(b, file_contents);
356359 } else {
357 std.log.warn("config.h could not be located automatically. Consider providing it explicitly via \"-Dconfig_h\"", .{});
360 std.log.warn("config.h could not be located automatically", .{});
361 std.log.info("config.h can be provided explicitly via \"-Dconfig_h\"", .{});
358362 break :blk null;
359363 }
360364 };
lib/compiler/Maker.zig+119-16
......@@ -199,12 +199,13 @@ pub fn main(init: process.Init.Minimal) !void {
199199 .random_seed = parseRandomSeed(seed_arg),
200200 };
201201
202 const cmd = stringToEnum(enum { libc, init, fetch, build }, cmd_name) orelse
202 const cmd = stringToEnum(enum { libc, init, fetch, build, @"cache-cat" }, cmd_name) orelse
203203 fatal("bad command name: {q}", .{cmd_name});
204204 switch (cmd) {
205205 .libc => return cmdLibC(gpa, &graph, args[arg_i..]),
206206 .init => return cmdInit(gpa, &graph, args[arg_i..]),
207207 .fetch => return cmdFetch(gpa, &graph, args[arg_i..]),
208 .@"cache-cat" => return cmdCacheCat(gpa, &graph, args[arg_i..]),
208209 .build => {},
209210 }
210211
......@@ -702,8 +703,8 @@ pub fn main(init: process.Init.Minimal) !void {
702703 configure: while (true) {
703704 // Set of files that, if modified, imply that recompiling and rerunning
704705 // configurer is needed.
705 var configure_source_files: Cache.Manifest.Files = .empty;
706 defer Cache.Manifest.freeFiles(gpa, &configure_source_files);
706 var configure_source_files: Cache.Manifest.SelfContainedFiles = .empty;
707 defer configure_source_files.deinit(gpa);
707708
708709 // If this fails, we can still start the server and wait for user
709710 // to request a rebuild. If it returns error.FailedButCacheIntact
......@@ -1043,7 +1044,7 @@ const ConfigureOptions = struct {
10431044 fetch_only: bool,
10441045 print_configuration: PrintConfiguration,
10451046 forks: []Fork,
1046 src_files: *Cache.Manifest.Files,
1047 src_files: *Cache.Manifest.SelfContainedFiles,
10471048};
10481049
10491050fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
......@@ -1398,8 +1399,14 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
13981399 defer compile_prog_node.end();
13991400
14001401 if (config_man) |man| {
1401 if (try man.hit(compile_prog_node)) {
1402 const digest = man.final();
1402 var diagnostic: Cache.Manifest.CheckDiagnostic = undefined;
1403 const status = man.check(&diagnostic, compile_prog_node) catch |err| switch (err) {
1404 error.Canceled, error.OutOfMemory => |e| return e,
1405 error.CacheCheckFailed => fatal("checking cache failed: {f}", .{diagnostic.fmt(man)}),
1406 };
1407 log.debug("configuration cache {f}", .{status.fmt(man)});
1408 if (status == .hit) {
1409 const digest = man.hitDigestHex();
14031410 const path: Path = .{
14041411 .root_dir = graph.local_cache_root,
14051412 .sub_path = try arena.print("c/{s}", .{&digest}),
......@@ -1497,11 +1504,12 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
14971504 }
14981505
14991506 if (config_man) |man| for (configuration.path_deps) |path_dep| {
1500 switch (path_dep.flags.mode) {
1501 .directory => {}, // TODO
1502 .contents => try man.addPathPost(try confPathDepToCachePath(arena, graph, &configuration, path_dep)),
1503 .metadata => {}, // TODO
1504 }
1507 const path = try confPathDepToCachePath(arena, graph, &configuration, path_dep);
1508 try man.addDiscoveredPath(.{
1509 .discovered_path = .{ .unresolved = path },
1510 .handle = if (path_dep.flags.is_directory) .{ .dir = null } else .{ .file = null },
1511 .metadata_only = path_dep.flags.metadata_only,
1512 });
15051513 };
15061514
15071515 // If it is poisoned, there is no point in moving it to cached
......@@ -1510,7 +1518,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
15101518 break :cp .{ config_tmp_path, null };
15111519 } else {
15121520 const man = config_man.?;
1513 const digest = man.final();
1521 const digest = man.missDigestHex();
15141522 const final_path: Path = .{
15151523 .root_dir = graph.local_cache_root,
15161524 .sub_path = try arena.print("c/{s}", .{&digest}),
......@@ -1541,7 +1549,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
15411549 config_tmp_path, final_path, e,
15421550 });
15431551 };
1544 man.writeManifest() catch |err| log.warn("failed to write cache manifest: {t}", .{err});
1552 man.finalize() catch |err| log.warn("failed to write cache manifest: {t}", .{err});
15451553 options.src_files.* = man.takeFiles();
15461554 break :cp .{ final_path, man.toOwnedLock() };
15471555 }
......@@ -1563,10 +1571,10 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
15631571
15641572 const configuration = c: {
15651573 var file = configuration_path.root_dir.handle.openFile(io, configuration_path.sub_path, .{}) catch |err|
1566 fatal("failed to open configuration file {f}: {t}", .{ configuration_path, err });
1574 fatal("failed to open configuration file {qf}: {t}", .{ configuration_path, err });
15671575 defer file.close(io);
15681576 break :c Configuration.loadFile(arena, io, file) catch |err|
1569 fatal("failed to load configuration file {f}: {t}", .{ configuration_path, err });
1577 fatal("failed to load configuration file {qf}: {t}", .{ configuration_path, err });
15701578 };
15711579 // Technically if the configuration is marked as poisoned, we could
15721580 // already delete the file now, but we leave it around in case the
......@@ -1879,6 +1887,101 @@ fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
18791887 return process.cleanExit(io);
18801888}
18811889
1890fn cmdCacheCat(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
1891 const io = graph.io;
1892
1893 var arg_i: usize = 0;
1894 var contents: std.ArrayList(u8) = .empty;
1895 defer contents.deinit(gpa);
1896
1897 while (nextArg(args, &arg_i)) |arg| {
1898 if (mem.startsWith(u8, arg, "-")) {
1899 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
1900 try Io.File.stdout().writeStreamingAll(io, usage_cache_cat);
1901 return process.cleanExit(io);
1902 } else {
1903 fatal("unrecognized parameter: {q}", .{arg});
1904 }
1905 } else {
1906 var file = Dir.cwd().openFile(io, arg, .{}) catch |err| fatal("opening {q} failed: {t}", .{ arg, err });
1907 defer file.close(io);
1908
1909 var manifest_reader = file.reader(io, &.{}); // Reads positionally from zero.
1910 contents.clearRetainingCapacity();
1911 manifest_reader.interface.appendRemainingUnlimited(gpa, &contents) catch |err| switch (err) {
1912 error.OutOfMemory => |e| return e,
1913 error.ReadFailed => switch (manifest_reader.err.?) {
1914 error.Canceled => |e| return e,
1915 else => |e| fatal("reading from {q} failed: {t}", .{ arg, e }),
1916 },
1917 };
1918 const hex_digest = Dir.path.basename(arg);
1919 cacheCatOne(hex_digest, contents.items, initStdoutWriter(io)) catch |err| switch (err) {
1920 error.WriteFailed => fatal("writing to stdout failed: {t}", .{stdout_writer_allocation.err.?}),
1921 else => |e| fatal("parsing {q} failed: {t}", .{ arg, e }),
1922 };
1923 try stdout_writer_allocation.flush();
1924 }
1925 }
1926}
1927
1928fn cacheCatOne(input_hex_digest: []const u8, contents: []const u8, writer: *Io.Writer) !void {
1929 var bin_digest: Cache.BinDigest = undefined;
1930 _ = try fmt.hexToBytes(&bin_digest, input_hex_digest);
1931
1932 var hh: Cache.HashHelper = .{};
1933 hh.hasher.update(&bin_digest);
1934
1935 var serializer: std.zon.Serializer = .{ .writer = writer };
1936 var top_level = try serializer.beginStruct(.{});
1937 try top_level.field("input_hash", input_hex_digest, .{});
1938 var files_tuple = try top_level.beginTupleField("files", .{});
1939 var off: usize = 0;
1940 while (off + 1 < contents.len) {
1941 const file_off: Cache.Manifest.File.Offset = @fromBackingInt(@intCast(off));
1942 const file = try file_off.getFallibleConst(contents);
1943 const path = try file_off.pathFallible(contents);
1944 if (path.len == 0) return error.InvalidFormat;
1945
1946 var file_obj = try files_tuple.beginStructField(.{ .whitespace_style = .{ .wrap = false } });
1947 try file_obj.field("size", file.size, .{});
1948 try file_obj.field("inode", file.inode, .{});
1949 try file_obj.field("mtime", file.mtime, .{});
1950 const hex_digest = Cache.binToHex(file.digest);
1951 try file_obj.field("digest", @as([]const u8, &hex_digest), .{});
1952 if (file.flags.is_directory) try file_obj.field("directory", true, .{});
1953 if (file.flags.metadata_only) try file_obj.field("metadata", true, .{});
1954 try file_obj.field("prefix", file.flags.prefix, .{});
1955 try file_obj.field("path", path, .{});
1956 try file_obj.end();
1957
1958 hh.hasher.update(&file.digest);
1959
1960 off += Cache.Manifest.File.sizeOf(path.len);
1961 }
1962
1963 try files_tuple.end();
1964
1965 var discovered_bin_digest: Cache.BinDigest = undefined;
1966 hh.hasher.final(&discovered_bin_digest);
1967 const discovered_hex_digest = Cache.binToHex(discovered_bin_digest);
1968 try top_level.field("discovered_hash", @as([]const u8, &discovered_hex_digest), .{});
1969
1970 try top_level.end();
1971 try writer.writeByte('\n');
1972}
1973
1974const usage_cache_cat =
1975 \\Usage: zig cache-cat <paths>
1976 \\
1977 \\ Prints .zig-cache/h/* manifest files in text form.
1978 \\
1979 \\Options:
1980 \\ -h, --help Print this help and exit
1981 \\
1982 \\
1983;
1984
18821985const usage_fetch =
18831986 \\Usage: zig fetch [options] <url>
18841987 \\Usage: zig fetch [options] <path>
......@@ -2220,7 +2323,7 @@ fn resolveTopLevelSteps(maker: *Maker, step_names: []const []const u8) ![]const
22202323fn prepare(
22212324 maker: *Maker,
22222325 step_indices: []const Configuration.Step.Index,
2223 configure_source_files: *const Cache.Manifest.Files,
2326 configure_source_files: *const Cache.Manifest.SelfContainedFiles,
22242327) !void {
22252328 const gpa = maker.gpa;
22262329 const graph = maker.graph;
lib/compiler/Maker/Step.zig+27-32
......@@ -726,53 +726,48 @@ pub fn handleChildProcessTerm(s: *Step, maker: *Maker, term: std.process.Child.T
726726/// Prefer `cacheHitWatched` unless you already added watch inputs
727727/// separately from using the cache system.
728728pub fn cacheHit(s: *Step, maker: *Maker, man: *Cache.Manifest, parent_progress_node: std.Progress.Node) !bool {
729 s.result_cached = man.hit(parent_progress_node) catch |err| return failWithCacheError(s, maker, man, err);
730 return s.result_cached;
729 var diag: Cache.Manifest.CheckDiagnostic = undefined;
730 const status = man.check(&diag, parent_progress_node) catch |err|
731 return failWithCacheError(s, maker, man, &diag, err);
732 const hit = .hit == status; // TODO cache miss reason in the build summary
733 s.result_cached = hit;
734 return hit;
731735}
732736
733737/// Clears previous watch inputs, if any, and then populates watch inputs from
734738/// the full set of files picked up by the cache manifest.
735739///
736/// Must be accompanied with `writeManifestAndWatch`.
740/// Must be accompanied with `finalizeManifestAndWatch`.
737741pub fn cacheHitWatched(s: *Step, maker: *Maker, man: *Cache.Manifest, parent_progress_node: std.Progress.Node) !bool {
738 const is_hit = man.hit(parent_progress_node) catch |err| return failWithCacheError(s, maker, man, err);
739 s.result_cached = is_hit;
742 var diag: Cache.Manifest.CheckDiagnostic = undefined;
743 const status = man.check(&diag, parent_progress_node) catch |err|
744 return failWithCacheError(s, maker, man, &diag, err);
745 const hit = .hit == status; // TODO cache miss reason in the build summary
746 s.result_cached = hit;
740747 // The above call to hit() populates the manifest with files, so in case of
741748 // a hit, we need to populate watch inputs.
742 if (is_hit) try setWatchInputsFromManifest(s, maker, man);
743 return is_hit;
749 if (hit) try setWatchInputsFromManifest(s, maker, man);
750 return hit;
744751}
745752
746753fn failWithCacheError(
747754 s: *Step,
748755 maker: *Maker,
749756 man: *const Cache.Manifest,
750 err: Cache.Manifest.HitError,
757 diag: *const Cache.Manifest.CheckDiagnostic,
758 err: Cache.Manifest.CheckError,
751759) error{ OutOfMemory, Canceled, MakeFailed } {
752760 switch (err) {
753 error.CacheCheckFailed => switch (man.diagnostic) {
754 .none => unreachable,
755 .manifest_create, .manifest_read, .manifest_lock => |e| return s.fail(maker, "failed checking cache: {t} {t}", .{
756 man.diagnostic, e,
757 }),
758 .file_open, .file_stat, .file_read, .file_hash => |op| {
759 const pp = man.files.keys()[op.file_index].prefixed_path;
760 const prefix = man.cache.prefixes()[pp.prefix].path orelse "";
761 return s.fail(maker, "failed checking cache: {s}{c}{s} {t} {t}", .{
762 prefix, Dir.path.sep, pp.sub_path, man.diagnostic, op.err,
763 });
764 },
765 },
761 error.CacheCheckFailed => return s.fail(maker, "checking cache failed: {f}", .{diag.fmt(man)}),
766762 error.OutOfMemory, error.Canceled => |e| return e,
767 error.InvalidFormat => return s.fail(maker, "failed checking cache: invalid manifest file format", .{}),
768763 }
769764}
770765
771/// Prefer `writeManifestAndWatch` unless you already added watch inputs
766/// Prefer `finalizeManifestAndWatch` unless you already added watch inputs
772767/// separately from using the cache system.
773pub fn writeManifest(s: *Step, maker: *Maker, man: *Cache.Manifest) !void {
768pub fn finalizeManifest(s: *Step, maker: *Maker, man: *Cache.Manifest) !void {
774769 if (s.test_results.isSuccess()) {
775 man.writeManifest() catch |err| switch (err) {
770 man.finalize() catch |err| switch (err) {
776771 error.Canceled => |e| return e,
777772 else => |e| try s.addError(maker, "failed writing cache manifest: {t}", .{e}),
778773 };
......@@ -783,29 +778,29 @@ pub fn writeManifest(s: *Step, maker: *Maker, man: *Cache.Manifest) !void {
783778/// the full set of files picked up by the cache manifest.
784779///
785780/// Must be accompanied with `cacheHitWatched`.
786pub fn writeManifestAndWatch(s: *Step, maker: *Maker, man: *Cache.Manifest) !void {
787 try writeManifest(s, maker, man);
781pub fn finalizeManifestAndWatch(s: *Step, maker: *Maker, man: *Cache.Manifest) !void {
782 try finalizeManifest(s, maker, man);
788783 try setWatchInputsFromManifest(s, maker, man);
789784}
790785
791786pub fn setWatchInputsFromManifest(s: *Step, maker: *Maker, man: *Cache.Manifest) !void {
792 return setWatchInputsFromManifestFiles(s, maker, &man.files, man.cache.prefixes());
787 return setWatchInputsFromManifestFiles(s, maker, &man.borrowFiles(), man.cache.prefixes());
793788}
794789
795790pub fn setWatchInputsFromManifestFiles(
796791 s: *Step,
797792 maker: *Maker,
798 files: *const Cache.Manifest.Files,
793 scf: *const Cache.Manifest.SelfContainedFiles,
799794 prefixes: []const Cache.Directory,
800795) !void {
801796 const graph = maker.graph;
802797 const arena = graph.arena; // TODO don't leak into process arena
803798 clearWatchInputs(s, maker);
804 for (files.keys()) |file| {
799 for (scf.files.keys()) |file_offset| {
805800 // The file path data is freed when the cache manifest is cleaned up at the end of `make`.
806 const sub_path = try arena.dupe(u8, file.prefixed_path.sub_path);
801 const sub_path = try arena.dupe(u8, scf.path(file_offset));
807802 try addWatchInputFromPath(s, maker, .{
808 .root_dir = prefixes[file.prefixed_path.prefix],
803 .root_dir = prefixes[file_offset.get(scf.contents.items).flags.prefix],
809804 .sub_path = Dir.path.dirname(sub_path) orelse "",
810805 }, Dir.path.basename(sub_path));
811806 }
lib/compiler/Maker/Step/ConfigHeader.zig+3-3
......@@ -137,7 +137,7 @@ pub fn make(
137137 man.hash.addBytes(output);
138138
139139 if (try step.cacheHit(maker, &man, progress_node)) {
140 const digest = man.final();
140 const digest = man.hitDigestHex();
141141 maker.generatedPath(conf_ch.generated_dir).* = .{
142142 .root_dir = cache_root,
143143 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
......@@ -145,7 +145,7 @@ pub fn make(
145145 return;
146146 }
147147
148 const digest = man.final();
148 const digest = man.missDigestHex();
149149
150150 // If output_path has directory parts, deal with them. Example:
151151 // output_dir is zig-cache/o/HASH
......@@ -169,7 +169,7 @@ pub fn make(
169169 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
170170 };
171171
172 try step.writeManifest(maker, &man);
172 try step.finalizeManifest(maker, &man);
173173}
174174
175175fn ensureAllValuesUsed(
lib/compiler/Maker/Step/ObjCopy.zig+4-4
......@@ -34,7 +34,7 @@ pub fn make(
3434 defer man.deinit();
3535
3636 const input_path = try maker.resolveLazyPath(arena, input_lazy_path, step_index);
37 _ = try man.addFilePath(input_path, null);
37 _ = try man.addInputPath(input_path, .{});
3838 man.hash.addOptionalBytes(only_section);
3939 man.hash.addOptionalBytes(opt_basename);
4040 man.hash.addOptionalBytes(opt_debug_basename);
......@@ -48,7 +48,7 @@ pub fn make(
4848
4949 if (try step.cacheHit(maker, &man, progress_node)) {
5050 // Cache hit, skip subprocess execution.
51 const digest = man.final();
51 const digest = man.hitDigestHex();
5252 maker.generatedPath(conf_oc.output_file).* = .{
5353 .root_dir = cache_root,
5454 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
......@@ -67,7 +67,7 @@ pub fn make(
6767
6868 // We don't find out more input files while executing objcopy so we can
6969 // already obtain the digest and use it directly as the output path.
70 const digest = man.final();
70 const digest = man.missDigestHex();
7171 const dest_path: Path = .{
7272 .root_dir = cache_root,
7373 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
......@@ -165,7 +165,7 @@ pub fn make(
165165
166166 maker.generatedPath(conf_oc.output_file).* = dest_path;
167167
168 step.writeManifest(maker, &man) catch |err| switch (err) {
168 step.finalizeManifest(maker, &man) catch |err| switch (err) {
169169 error.Canceled => |e| return e,
170170 else => |e| try step.addError(maker, "failed writing cache manifest: {t}", .{e}),
171171 };
lib/compiler/Maker/Step/Options.zig+4-4
......@@ -42,7 +42,7 @@ pub fn make(
4242 const lazy_path = arg.path.get(conf);
4343 try step.addWatchInput(maker, arena, lazy_path);
4444 const arg_path = try maker.resolveLazyPath(arena, lazy_path, step_index);
45 _ = try man.addFilePath(arg_path, null);
45 _ = try man.addInputPath(arg_path, .{});
4646 try args_bytes.print(arena, "pub const {f}: []const u8 = \"{f}\";\n", .{
4747 std.zig.fmtId(name), arg_path.fmtEscapeString(),
4848 });
......@@ -54,7 +54,7 @@ pub fn make(
5454 const basename = "options.zig";
5555
5656 if (try step.cacheHitWatched(maker, &man, progress_node)) {
57 const digest = man.final();
57 const digest = man.hitDigestHex();
5858 maker.generatedPath(conf_options.generated_file).* = .{
5959 .root_dir = cache_root,
6060 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
......@@ -63,7 +63,7 @@ pub fn make(
6363 return;
6464 }
6565
66 const digest = man.final();
66 const digest = man.missDigestHex();
6767 const out_path: Cache.Path = .{
6868 .root_dir = cache_root,
6969 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
......@@ -95,7 +95,7 @@ pub fn make(
9595 },
9696 };
9797
98 try step.writeManifestAndWatch(maker, &man);
98 try step.finalizeManifestAndWatch(maker, &man);
9999
100100 maker.generatedPath(conf_options.generated_file).* = out_path;
101101}
lib/compiler/Maker/Step/Run.zig+36-20
......@@ -97,7 +97,7 @@ pub fn make(
9797 man.hash.add(arg.flags.make_absolute);
9898 man.hash.addBytesZ(prefix);
9999 man.hash.addBytesZ(suffix);
100 _ = try man.addFilePath(file_path, null);
100 _ = try man.addInputPath(file_path, .{});
101101 },
102102 .path_directory => {
103103 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
......@@ -135,7 +135,7 @@ pub fn make(
135135 argv_list.appendAssumeCapacity(result.written());
136136 man.hash.addBytesZ(prefix);
137137 man.hash.addBytesZ(suffix);
138 _ = try man.addFilePath(file_path, null);
138 _ = try man.addInputPath(file_path, .{});
139139 },
140140 .artifact => {
141141 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
......@@ -155,7 +155,7 @@ pub fn make(
155155 man.hash.add(arg.flags.make_absolute);
156156 man.hash.addBytesZ(prefix);
157157 man.hash.addBytesZ(suffix);
158 _ = try man.addFilePath(file_path, null);
158 _ = try man.addInputPath(file_path, .{});
159159 },
160160 .output_file, .output_directory => {
161161 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
......@@ -211,7 +211,7 @@ pub fn make(
211211 },
212212 .lazy_path => |lazy_path| {
213213 const file_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);
214 _ = try man.addFilePath(file_path, null);
214 _ = try man.addInputPath(file_path, .{});
215215 },
216216 .none => {},
217217 }
......@@ -240,7 +240,7 @@ pub fn make(
240240
241241 for (conf_run.file_inputs.slice) |lazy_path| {
242242 const file_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);
243 _ = try man.addFilePath(file_path, null);
243 _ = try man.addInputPath(file_path, .{});
244244 }
245245
246246 if (conf_run.cwd.value) |lazy_path| {
......@@ -261,7 +261,7 @@ pub fn make(
261261
262262 if (!has_side_effects and try step.cacheHitWatched(maker, &man, progress_node)) {
263263 // Cache hit; skip running command.
264 const digest = man.final();
264 const digest = man.hitDigestHex();
265265 try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest);
266266 try populateGeneratedPaths(maker, output_placeholders.items, cache_root, &digest);
267267 step.result_cached = true;
......@@ -270,12 +270,12 @@ pub fn make(
270270
271271 if (!any_dep_files) {
272272 // We already know the final output paths; use them directly.
273 const digest = if (has_side_effects) man.hash.final() else man.final();
273 const digest = if (has_side_effects) man.hash.final() else man.missDigestHex();
274274 const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest;
275275 try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest);
276276 try populateGeneratedPathsCreateDirs(arena, run_index, maker, output_dir_path, output_placeholders.items, argv_list.items);
277277 try runCommand(arena, run, run_index, maker, progress_node, argv_list.items, has_side_effects, output_dir_path, null);
278 if (!has_side_effects) try step.writeManifestAndWatch(maker, &man);
278 if (!has_side_effects) try step.finalizeManifestAndWatch(maker, &man);
279279 return;
280280 }
281281
......@@ -292,23 +292,39 @@ pub fn make(
292292 switch (arg.flags.tag) {
293293 .output_file => if (arg.flags.dep_file) {
294294 const generated_path = maker.generatedPath(arg.generated.value.?).*;
295 const result = if (has_side_effects)
296 man.addDepFile(generated_path.root_dir.handle, generated_path.sub_path)
297 else
298 man.addDepFilePost(generated_path.root_dir.handle, generated_path.sub_path);
299 result catch |err| switch (err) {
300 error.OutOfMemory, error.Canceled => |e| return e,
301 else => |e| return step.fail(maker, "failed adding to cache the file {f}: {t}", .{
302 generated_path, e,
303 }),
304 };
295 if (has_side_effects) {
296 var diagnostic: Cache.DepTokenizer.Token = undefined;
297 man.addInputDepFile(generated_path, &diagnostic) catch |err| switch (err) {
298 error.OutOfMemory, error.Canceled => |e| return e,
299 error.InvalidDepFile => return step.fail(maker, "failed adding dep file {f} to cache: {f}", .{
300 generated_path, diagnostic,
301 }),
302 else => |e| return step.fail(maker, "failed adding dep file {f} to cache: {t}", .{
303 generated_path, e,
304 }),
305 };
306 } else {
307 var diagnostic: Cache.Manifest.AddDiscoveredDepFileDiagnostic = undefined;
308 man.addDiscoveredDepFile(generated_path, &diagnostic) catch |err| switch (err) {
309 error.OutOfMemory, error.Canceled => |e| return e,
310 error.InvalidDepFile => return step.fail(maker, "failed adding dep file {f} to cache: {f}", .{
311 generated_path, diagnostic.dep_tokenizer,
312 }),
313 error.FileSystemFailure => return step.fail(maker, "failed adding a path from dep file {f} to cache: {f}", .{
314 generated_path, diagnostic.add_discovered_path,
315 }),
316 else => |e| return step.fail(maker, "failed adding dep file {f} to cache: {t}", .{
317 generated_path, e,
318 }),
319 };
320 }
305321 },
306322 .output_directory => continue,
307323 else => unreachable,
308324 }
309325 }
310326
311 const digest = if (has_side_effects) man.hash.final() else man.final();
327 const digest = if (has_side_effects) man.hash.final() else man.missDigestHex();
312328
313329 const any_output = output_placeholders.items.len > 0 or
314330 conf_run.captured_stdout.value != null or conf_run.captured_stderr.value != null;
......@@ -344,7 +360,7 @@ pub fn make(
344360 };
345361 }
346362
347 if (!has_side_effects) try step.writeManifestAndWatch(maker, &man);
363 if (!has_side_effects) try step.finalizeManifestAndWatch(maker, &man);
348364
349365 try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest);
350366 try populateGeneratedPaths(maker, output_placeholders.items, cache_root, &digest);
lib/compiler/Maker/Step/WriteFile.zig+5-5
......@@ -55,7 +55,7 @@ pub fn make(
5555 man.hash.addBytes(copy.sub_path.slice(conf));
5656 const src_lazy_path = copy.src_file.get(conf);
5757 const source_path = try maker.resolveLazyPath(arena, src_lazy_path, step_index);
58 _ = try man.addFilePath(source_path, null);
58 _ = try man.addInputPath(source_path, .{});
5959 try step.addWatchInput(maker, arena, src_lazy_path);
6060 }
6161
......@@ -96,7 +96,7 @@ pub fn make(
9696 },
9797 .file => {
9898 const entry_path = try src_dir_path.join(arena, entry.path);
99 _ = try man.addFilePath(entry_path, null);
99 _ = try man.addInputPath(entry_path, .{});
100100 total_items += 1;
101101 },
102102 else => continue,
......@@ -105,7 +105,7 @@ pub fn make(
105105 }
106106
107107 if (try step.cacheHit(maker, &man, progress_node)) {
108 const digest = man.final();
108 const digest = man.hitDigestHex();
109109 maker.generatedPath(conf_wf.generated_directory).* = .{
110110 .root_dir = cache_root,
111111 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
......@@ -114,7 +114,7 @@ pub fn make(
114114 return;
115115 }
116116
117 const digest = man.final();
117 const digest = man.missDigestHex();
118118 const out_path: Path = .{
119119 .root_dir = cache_root,
120120 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
......@@ -122,7 +122,7 @@ pub fn make(
122122
123123 progress_node.setEstimatedTotalItems(total_items);
124124 try operate(maker, step_index, open_dir_cache, out_path, progress_node);
125 try step.writeManifest(maker, &man);
125 try step.finalizeManifest(maker, &man);
126126
127127 maker.generatedPath(conf_wf.generated_directory).* = out_path;
128128 },
lib/std/Build.zig+42-6
......@@ -58,7 +58,8 @@ available_deps: AvailableDeps,
5858
5959pub const ConfigureDependency = struct {
6060 lazy_path: LazyPath,
61 mode: std.Build.Configuration.PathDep.Mode,
61 is_directory: bool,
62 metadata_only: bool,
6263};
6364
6465pub const ReleaseMode = enum {
......@@ -102,7 +103,8 @@ pub const Graph = struct {
102103 /// Populated by calling one of:
103104 /// * `dependOnFileContents`
104105 /// * `dependOnFileMetadata`
105 /// * `dependOnDirectory`
106 /// * `dependOnDirectoryContents`
107 /// * `dependOnDirectoryMetadata`
106108 configure_dependencies: ArrayList(ConfigureDependency) = .empty,
107109
108110 /// If the cache is poisoned means that the **configure logic** had side
......@@ -2602,7 +2604,8 @@ pub fn dependOnFileContents(b: *Build, lazy_path: LazyPath) void {
26022604 const graph = b.graph;
26032605 graph.configure_dependencies.append(graph.arena, .{
26042606 .lazy_path = lazy_path.dupe(graph),
2605 .mode = .contents,
2607 .is_directory = false,
2608 .metadata_only = false,
26062609 }) catch @panic("OOM");
26072610}
26082611
......@@ -2627,7 +2630,8 @@ pub fn dependOnFileMetadata(b: *Build, lazy_path: LazyPath) void {
26272630 const graph = b.graph;
26282631 graph.configure_dependencies.append(graph.arena, .{
26292632 .lazy_path = lazy_path.dupe(graph),
2630 .mode = .metadata,
2633 .is_directory = false,
2634 .metadata_only = true,
26312635 }) catch @panic("OOM");
26322636}
26332637
......@@ -2636,6 +2640,35 @@ pub fn dependOnFileMetadata(b: *Build, lazy_path: LazyPath) void {
26362640/// This is an alternative to `Graph.poisonCache` that avoids making every invocation
26372641/// of `zig build` into a cache miss.
26382642///
2643/// If any file is created, deleted, or renamed in this directory, leaving the
2644/// directory in a different state than last configuration with respect to
2645/// existence and naming of entries, the configure phase will be repeated.
2646///
2647/// Only a subset of `LazyPath` are supported:
2648/// - Relative to cwd
2649/// - Relative to any package root
2650/// - Relative to zig cache or zig installation
2651///
2652/// If the directory would be inside one of the search prefixes, then the dependency
2653/// cannot be tracked; `Graph.poisonCache` must be used instead.
2654///
2655/// Not recursive.
2656pub fn dependOnDirectoryContents(b: *Build, lazy_path: LazyPath) void {
2657 validateConfigureDependency(lazy_path);
2658 const graph = b.graph;
2659 graph.configure_dependencies.append(graph.arena, .{
2660 .lazy_path = lazy_path.dupe(graph),
2661 .is_directory = true,
2662 .metadata_only = false,
2663 }) catch @panic("OOM");
2664}
2665
2666/// Indicates that the build.zig logic depends on a particular directory's last
2667/// modification date.
2668///
2669/// This is an alternative to `Graph.poisonCache` that avoids making every invocation
2670/// of `zig build` into a cache miss.
2671///
26392672/// If any file is created, deleted, or renamed in this directory, the
26402673/// configure phase will be repeated.
26412674///
......@@ -2646,12 +2679,15 @@ pub fn dependOnFileMetadata(b: *Build, lazy_path: LazyPath) void {
26462679///
26472680/// If the directory would be inside one of the search prefixes, then the dependency
26482681/// cannot be tracked; `Graph.poisonCache` must be used instead.
2649pub fn dependOnDirectory(b: *Build, lazy_path: LazyPath) void {
2682///
2683/// Not recursive.
2684pub fn dependOnDirectoryMetadata(b: *Build, lazy_path: LazyPath) void {
26502685 validateConfigureDependency(lazy_path);
26512686 const graph = b.graph;
26522687 graph.configure_dependencies.append(graph.arena, .{
26532688 .lazy_path = lazy_path.dupe(graph),
2654 .mode = .directory,
2689 .is_directory = true,
2690 .metadata_only = true,
26552691 }) catch @panic("OOM");
26562692}
26572693
lib/std/Build/Cache.zig+1920-885
......@@ -48,8 +48,7 @@ pub fn obtain(cache: *Cache) Manifest {
4848 .cache = cache,
4949 .hash = cache.hash,
5050 .manifest_file = null,
51 .manifest_dirty = false,
52 .hex_digest = undefined,
51 .state = .input,
5352 };
5453}
5554
......@@ -57,71 +56,66 @@ pub fn prefixes(cache: *const Cache) []const Directory {
5756 return cache.prefixes_buffer[0..cache.prefixes_len];
5857}
5958
60pub const PrefixedPath = struct {
61 prefix: u8,
62 sub_path: []const u8,
63
64 fn eql(a: PrefixedPath, b: PrefixedPath) bool {
65 return a.prefix == b.prefix and std.mem.eql(u8, a.sub_path, b.sub_path);
66 }
59const PrefixIndex = u6;
6760
68 fn hash(pp: PrefixedPath) u32 {
69 return @truncate(std.hash.Wyhash.hash(pp.prefix, pp.sub_path));
70 }
61const PrefixedPath = struct {
62 prefix: PrefixIndex,
63 sub_path: []const u8,
7164};
7265
73fn findPrefixPath(cache: *const Cache, path: Path) !PrefixedPath {
74 const gpa = cache.gpa;
75 const resolved_path = try std.fs.path.resolve(gpa, &.{
76 cache.cwd, path.root_dir.path orelse ".", path.subPathOrDot(),
77 });
78 errdefer gpa.free(resolved_path);
79 return findPrefixResolved(cache, resolved_path);
80}
81
82fn findPrefix(cache: *const Cache, file_path: []const u8) !PrefixedPath {
83 const gpa = cache.gpa;
84 const resolved_path = try std.fs.path.resolve(gpa, &.{file_path});
85 errdefer gpa.free(resolved_path);
86 return findPrefixResolved(cache, resolved_path);
66fn appendPrefixedPath(cache: *const Cache, contents: *std.ArrayList(u8), prefixed_path: PrefixedPath) !PrefixIndex {
67 const end = contents.items.len + prefixed_path.sub_path.len;
68 const needed_alignment = @alignOf(Manifest.File) - (end % @alignOf(Manifest.File));
69 assert(needed_alignment >= 1); // Always need at least a null byte.
70 // Extra +1 here is due to the requirement to keep at least 1 unused capacity in `Manifest.contents` at all times.
71 try contents.ensureTotalCapacity(cache.gpa, end + needed_alignment + 1);
72 contents.appendSliceAssumeCapacity(prefixed_path.sub_path);
73 contents.appendNTimesAssumeCapacity(0, needed_alignment);
74 return prefixed_path.prefix;
8775}
8876
89/// Takes ownership of `resolved_path` on success.
90fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
77fn resolveAppendPath(cache: *const Cache, contents: *std.ArrayList(u8), path: Path) !PrefixIndex {
9178 const gpa = cache.gpa;
9279 const cwd = cache.cwd;
80 const path_start = contents.items.len;
81
82 const resolved_path = try std.fs.path.resolveAlloc(gpa, &.{
83 path.root_dir.path orelse cwd,
84 path.subPathOrDot(),
85 });
86 defer gpa.free(resolved_path);
87
9388 for (cache.prefixes(), 0..) |prefix, i| {
94 const p = prefix.path orelse continue;
95 const sub_path = getPrefixSubpath(gpa, cwd, p, resolved_path) catch |err| switch (err) {
96 error.NotASubPath => continue,
97 else => |e| return e,
98 };
99 // Free the resolved path since we're not going to return it
100 gpa.free(resolved_path);
101 return .{
102 .prefix = @intCast(i),
103 .sub_path = sub_path,
104 };
89 const pp = prefix.path orelse continue;
90 contents.shrinkRetainingCapacity(path_start);
91 try std.fs.path.relativeAppend(gpa, contents, cwd, null, pp, resolved_path);
92 const relative = contents.items[path_start..];
93
94 var component_iterator: std.fs.path.NativeComponentIterator = .init(relative);
95 if (component_iterator.root() != null) continue;
96 const first_component = component_iterator.first();
97 if (first_component != null and mem.eql(u8, first_component.?.name, "..")) continue;
98
99 const needed_alignment = @alignOf(Manifest.File) - (contents.items.len % @alignOf(Manifest.File));
100 assert(needed_alignment >= 1); // Always need at least a null byte.
101 // Extra +1 here is due to the requirement to keep at least 1 unused capacity in `Manifest.contents` at all
102 // times.
103 try contents.ensureUnusedCapacity(gpa, needed_alignment + 1);
104 contents.appendNTimesAssumeCapacity(0, needed_alignment);
105
106 return @intCast(i);
105107 }
106108
107 return .{
108 .prefix = 0,
109 .sub_path = resolved_path,
110 };
111}
109 contents.shrinkRetainingCapacity(path_start);
110 try contents.appendSlice(gpa, resolved_path);
112111
113fn getPrefixSubpath(gpa: Allocator, cwd: []const u8, prefix: []const u8, path: []u8) ![]u8 {
114 const relative = try std.fs.path.relative(gpa, cwd, null, prefix, path);
115 errdefer gpa.free(relative);
116 var component_iterator: std.fs.path.NativeComponentIterator = .init(relative);
117 if (component_iterator.root() != null) {
118 return error.NotASubPath;
119 }
120 const first_component = component_iterator.first();
121 if (first_component != null and std.mem.eql(u8, first_component.?.name, "..")) {
122 return error.NotASubPath;
123 }
124 return relative;
112 const needed_alignment = @alignOf(Manifest.File) - (contents.items.len % @alignOf(Manifest.File));
113 assert(needed_alignment >= 1); // Always need at least a null byte.
114 // Extra +1 here is due to the requirement to keep at least 1 unused capacity in `Manifest.contents` at all times.
115 try contents.ensureUnusedCapacity(gpa, needed_alignment + 1);
116 contents.appendNTimesAssumeCapacity(0, needed_alignment);
117
118 return 0;
125119}
126120
127121/// This is 128 bits - Even with 2^54 cache entries, the probably of a collision would be under 10^-6
......@@ -130,10 +124,6 @@ pub const hex_digest_len = bin_digest_len * 2;
130124pub const BinDigest = [bin_digest_len]u8;
131125pub const HexDigest = [hex_digest_len]u8;
132126
133/// This is currently just an arbitrary non-empty string that can't match another manifest line.
134const manifest_header = "0";
135pub const manifest_file_size_max = 100 * 1024 * 1024;
136
137127/// The type used for hashing file contents. Currently, this is SipHash128(1, 3), because it
138128/// provides enough collision resistance for the Manifest use cases, while being one of our
139129/// fastest options right now.
......@@ -143,56 +133,12 @@ pub const Hasher = crypto.auth.siphash.SipHash128(1, 3);
143133/// Refresh this with new random bytes when the manifest
144134/// format is modified in a non-backwards-compatible way.
145135pub const hasher_init: Hasher = Hasher.init(&.{
146 0x33, 0x52, 0xa2, 0x84,
147 0xcf, 0x17, 0x56, 0x57,
148 0x01, 0xbb, 0xcd, 0xe4,
149 0x77, 0xd6, 0xf0, 0x60,
136 0x02, 0xe9, 0xfa, 0xfe,
137 0xe0, 0x95, 0x81, 0x55,
138 0xf5, 0x5a, 0x15, 0xcb,
139 0x4a, 0xf4, 0x00, 0x09,
150140});
151141
152pub const File = struct {
153 prefixed_path: PrefixedPath,
154 max_file_size: ?usize,
155 /// Populated if the user calls `addOpenedFile`.
156 /// The handle is not owned here.
157 handle: ?Io.File,
158 stat: Stat,
159 bin_digest: BinDigest,
160 contents: ?[]const u8,
161
162 pub const Stat = struct {
163 inode: Io.File.INode,
164 size: u64,
165 mtime: Io.Timestamp,
166
167 pub fn fromFs(fs_stat: Io.File.Stat) Stat {
168 return .{
169 .inode = fs_stat.inode,
170 .size = fs_stat.size,
171 .mtime = fs_stat.mtime,
172 };
173 }
174 };
175
176 pub fn deinit(self: *File, gpa: Allocator) void {
177 gpa.free(self.prefixed_path.sub_path);
178 if (self.contents) |contents| {
179 gpa.free(contents);
180 self.contents = null;
181 }
182 self.* = undefined;
183 }
184
185 pub fn updateMaxSize(file: *File, new_max_size: ?usize) void {
186 const new = new_max_size orelse return;
187 file.max_file_size = if (file.max_file_size) |old| @max(old, new) else new;
188 }
189
190 pub fn updateHandle(file: *File, new_handle: ?Io.File) void {
191 const handle = new_handle orelse return;
192 file.handle = handle;
193 }
194};
195
196142pub const HashHelper = struct {
197143 hasher: Hasher = hasher_init,
198144
......@@ -299,7 +245,7 @@ pub const HashHelper = struct {
299245
300246pub fn binToHex(bin_digest: BinDigest) HexDigest {
301247 var out_digest: HexDigest = undefined;
302 var w: std.Io.Writer = .fixed(&out_digest);
248 var w: Io.Writer = .fixed(&out_digest);
303249 w.printHex(&bin_digest, .lower) catch unreachable;
304250 return out_digest;
305251}
......@@ -319,33 +265,337 @@ pub const Lock = struct {
319265 }
320266};
321267
268/// Format: a series of consecutive `Manifest.File`, followed by a final
269/// terminating zero byte to distinguish empty manifest file from manifest with
270/// zero files.
322271pub const Manifest = struct {
323272 cache: *Cache,
324273 /// Current state for incremental hashing.
325274 hash: HashHelper,
275 /// When this is null, `Manifest` is in "pre-check" phase. Otherwise it is in "post-check" phase.
326276 manifest_file: ?Io.File,
327 manifest_dirty: bool,
328 /// Set this flag to true before calling hit() in order to indicate that
329 /// upon a cache hit, the code using the cache will not modify the files
330 /// within the cache directory. This allows multiple processes to utilize
331 /// the same cache directory at the same time.
277 state: State,
278 /// Set this flag to true before calling `check` in order to indicate that upon a cache hit, the code
279 /// using the cache will not modify the files within the cache directory. This allows multiple processes
280 /// to utilize the same cache directory at the same time.
332281 want_shared_lock: bool = true,
333282 have_exclusive_lock: bool = false,
334283 // Indicate that we want isProblematicTimestamp to perform a filesystem write in
335284 // order to obtain a problematic timestamp for the next call. Calls after that
336285 // will then use the same timestamp, to avoid unnecessary filesystem writes.
337286 want_refresh_timestamp: bool = true,
338 files: Files = .{},
339 hex_digest: HexDigest,
340 diagnostic: Diagnostic = .none,
287 /// Uses `Cache.gpa`.
288 files: Files = .empty,
289 /// Indexes line up with `files`, but only up until `hit` is called. Uses
290 /// `Cache.gpa`.
291 input_paths: std.ArrayList(InputPath) = .empty,
341292 /// Keeps track of the last time we performed a file system write to observe
342293 /// what time the file system thinks it is, according to its own granularity.
343294 recent_problematic_timestamp: Io.Timestamp = .zero,
295 /// The entire manifest file contents, except for the final terminating
296 /// zero byte. However maintains always at least 1 unused capacity so the
297 /// final terminating byte can be added without allocation. Uses
298 /// `Cache.gpa`.
299 contents: std.ArrayList(u8) = .empty,
300 /// All contents from all `input_paths` whose contents were requested,
301 /// concatenated. Total byte size will be less than `max_input_content_len`
302 /// otherwise an error is returned.
303 ///
304 /// Data is invalidated when `addDiscoveredPath` is called.
305 all_input_content: std.ArrayList(u8) = .empty,
306 max_input_content_len: usize = std.math.maxInt(u32),
344307
345 pub const Diagnostic = union(enum) {
346 none,
308 pub const Files = std.array_hash_map.Custom(File.Offset, void, File.HashContext, false);
309
310 pub const State = enum {
311 /// Call `addInputPath`, `addInputPathOptional`, and methods of `Manifest.hash`. Once all input files and state
312 /// have been added, call `check`.
313 input,
314 /// `check` has already been called and a hit occurred. Call `hitDigest` to find cache artifact directory.
315 hit,
316 /// `hitDigest` has already been called. No further operations are allowed.
317 hit_digested,
318 /// `check` has already been called and a miss occurred. `missDigest` has not been called yet. Miss diagnostic
319 /// data may still be accessed. Call `addDiscoveredPath` to add discovered files and directories.
320 miss,
321 /// A miss occurred, `missDigest` has not been called yet. Call `addDiscoveredPath` to add more discovered files
322 /// and directories. Miss diagnostic data may no longer be accessed.
323 miss_discovered,
324 /// `missDigest` has been called, but `finalize` has not been called yet.
325 miss_digested,
326 /// The cache manifest has been written to disk such that the next check for the same inputs will yield a hit.
327 /// No further operations are allowed.
328 miss_finalized,
329 };
330
331 /// Source files and directories whose prefix and relative path are
332 /// included when computing the cache manifest digest. It's the information
333 /// needed to lazily hash the input files only when a cache miss occurs.
334 ///
335 /// `File.prefix`, `File.path`, and `File.mode` will be always populated,
336 /// but the other fields of `File` will be populated depending on the
337 /// fields of `InputPath`.
338 pub const InputPath = struct {
339 /// Determines whether `check` will keep the handle open or close it.
340 request_handle: bool,
341 /// Determines whether `handle` is populated.
342 have_handle: bool,
343 /// Determines whether `File.size`, `File.inode`, and `File.mtime` are populated.
344 have_stat: bool,
345 /// Determines whether `File.digest` is populated.
346 have_digest: bool,
347 contents: struct {
348 pop_off: enum(u32) { unpopulated = std.math.maxInt(u32), _ },
349 req_len: enum(u32) { unrequested = std.math.maxInt(u32) - 1, requested = std.math.maxInt(u32), _ },
350
351 pub const requested: @This() = .{
352 .pop_off = .unpopulated,
353 .req_len = .requested,
354 };
355
356 pub const unrequested: @This() = .{
357 .pop_off = .unpopulated,
358 .req_len = .unrequested,
359 };
360
361 pub fn populated(p: Populated) @This() {
362 return .{
363 .pop_off = @fromBackingInt(p.off),
364 .req_len = @fromBackingInt(p.len),
365 };
366 }
367
368 pub const Unwrapped = union(enum) {
369 unrequested,
370 requested,
371 populated: Populated,
372 };
373
374 pub const Populated = struct {
375 off: u32,
376 len: u32,
377 };
378
379 pub fn unwrap(this: @This()) Unwrapped {
380 return switch (this.pop_off) {
381 .unpopulated => switch (this.req_len) {
382 .unrequested => .unrequested,
383 .requested => .requested,
384 _ => unreachable,
385 },
386 _ => .{ .populated = .{
387 .off = @backingInt(this.pop_off),
388 .len = @backingInt(this.req_len),
389 } },
390 };
391 }
392
393 pub fn get(this: @This(), all_contents: []const u8) ?[]const u8 {
394 return switch (this.unwrap()) {
395 .unrequested, .requested => null,
396 .populated => |p| all_contents[p.off..][0..p.len],
397 };
398 }
399 },
400 /// `have_handle` determines whether this is populated.
401 handle: union {
402 file: Io.File,
403 dir: Io.Dir,
404 },
405
406 pub const Handle = union(enum) {
407 none,
408 file: Io.File,
409 dir: Io.Dir,
410 };
411
412 /// Index into `Manifest.input_paths`.
413 pub const Index = enum(u32) {
414 _,
415
416 pub fn get(i: @This(), manifest: *const Manifest) InputPath {
417 return manifest.input_paths.items[@backingInt(i)];
418 }
419
420 pub fn offset(i: @This(), manifest: *const Manifest) File.Offset {
421 return manifest.files.keys()[@backingInt(i)];
422 }
423
424 pub fn contents(i: @This(), manifest: *const Manifest) []const u8 {
425 const input_path = get(i, manifest);
426 return input_path.contents.get(manifest.all_input_content.items).?;
427 }
428 };
429
430 pub fn getHandle(this: @This(), file: *const File) Handle {
431 return if (!this.have_handle) .none else switch (file.flags.is_directory) {
432 false => .{ .file = this.handle.file },
433 true => .{ .dir = this.handle.dir },
434 };
435 }
436 };
437
438 /// The data per tracked input file that is stored in the manifest file.
439 pub const File = extern struct {
440 size: u64 align(1),
441 inode: Io.File.INode align(1),
442 /// Nanoseconds.
443 mtime: i64 align(1),
444 /// To simplify the hashing logic, this value is computed from size, inode, and mtime
445 /// in `hashFromMetadata` in the case that `Flags.metadata_only` is `true`.
446 digest: BinDigest align(1),
447 /// Starting with this field and continuing into the path, excluding the null byte,
448 /// is the string that is hashed for the manifest digest.
449 flags: Flags align(1),
450 /// Terminated by zero byte, then followed by padding until 8-byte aligned.
451 path_start: [0]u8 align(1),
452
453 pub const Flags = packed struct(u8) {
454 is_directory: bool,
455 metadata_only: bool,
456 prefix: PrefixIndex,
457 };
458
459 /// Prefixes path names in encoded directory contents. Starts numbering
460 /// at `1` so that null byte can be used unambiguously as entry
461 /// separator.
462 pub const Kind = enum(u8) {
463 file = 1,
464 directory = 2,
465 other = 3,
466
467 pub fn fromStat(kind: Io.File.Kind) @This() {
468 return switch (kind) {
469 .file => .file,
470 .directory => .directory,
471 else => .other,
472 };
473 }
474 };
475
476 /// Byte index within `Manifest.contents` where the entry starts.
477 pub const Offset = enum(u32) {
478 _,
479
480 pub fn get(offset: Offset, contents: []u8) *File {
481 return @constCast(getConst(offset, contents));
482 }
483
484 pub fn getConst(offset: Offset, contents: []const u8) *const File {
485 return @ptrCast(@alignCast(contents[@backingInt(offset)..][0..@sizeOf(File)]));
486 }
487
488 pub fn getFallible(offset: Offset, contents: []u8) error{InvalidFormat}!*File {
489 // TODO make @constCast support in-memory coercion across error unions and optionals
490 return @constCast(try getFallibleConst(offset, contents));
491 }
492
493 pub fn getFallibleConst(offset: Offset, contents: []const u8) error{InvalidFormat}!*const File {
494 if (@backingInt(offset) + @sizeOf(File) >= contents.len) return error.InvalidFormat;
495 if (!mem.isAligned(@backingInt(offset), @alignOf(File))) return error.InvalidFormat;
496 return getConst(offset, contents);
497 }
498
499 pub fn flagsAndPath(off: File.Offset, contents: []const u8) [:0]const u8 {
500 const flags_off = @offsetOf(File, "flags");
501 comptime assert(@offsetOf(File, "path_start") - flags_off == 1);
502 const start = @backingInt(off) + flags_off;
503 // Scan for the sentinel starting from the path_start offset because flags might be zero.
504 const end = mem.findScalarPos(u8, contents, start + 1, 0).?;
505 return contents[start..end :0];
506 }
507
508 pub fn pathFallible(off: File.Offset, contents: []const u8) error{InvalidFormat}![:0]const u8 {
509 const path_start = @backingInt(off) + @offsetOf(File, "path_start");
510 const path_end = mem.findScalarPos(u8, contents, path_start, 0) orelse return error.InvalidFormat;
511 return contents[path_start..path_end :0];
512 }
513
514 pub fn path(off: File.Offset, contents: []const u8) [:0]const u8 {
515 return pathFallible(off, contents) catch unreachable;
516 }
517 };
518
519 /// Intentionally matches if the files are different only by flags other than prefix.
520 pub const HashContext = struct {
521 contents: []const u8,
522
523 pub fn hash(this: @This(), off: Offset) u32 {
524 const file_prefix = off.getConst(this.contents).flags.prefix;
525 const file_path = off.path(this.contents);
526 return @truncate(std.hash.Wyhash.hash(file_prefix, file_path));
527 }
528
529 pub fn eql(this: @This(), a_off: Offset, b_off: Offset, b_index: usize) bool {
530 _ = b_index;
531 const a_prefix = a_off.getConst(this.contents).flags.prefix;
532 const b_prefix = b_off.getConst(this.contents).flags.prefix;
533 if (a_prefix != b_prefix) return false;
534 const a_path = a_off.path(this.contents);
535 const b_path = b_off.path(this.contents);
536 return mem.eql(u8, a_path, b_path);
537 }
538 };
539
540 fn hashFromMetadata(file: *File) void {
541 var hasher = hasher_init;
542 hasher.update(mem.asBytes(&file.size));
543 hasher.update(mem.asBytes(&file.inode));
544 hasher.update(mem.asBytes(&file.mtime));
545 hasher.final(&file.digest);
546 }
547
548 fn stat(file: *const File) Stat {
549 return .{
550 .size = file.size,
551 .inode = file.inode,
552 .mtime = .fromNanoseconds(file.mtime),
553 };
554 }
555
556 fn setStatUnchecked(file: *File, s: Stat) void {
557 file.size = s.size;
558 file.inode = s.inode;
559 file.mtime = @intCast(s.mtime.toNanoseconds());
560 }
561
562 fn setStat(file: *File, m: *Manifest, s: Stat) Io.Cancelable!void {
563 setStatUnchecked(file, s);
564
565 if (try m.isProblematicTimestamp(s.mtime)) {
566 // The actual file has an unreliable timestamp; force it to be hashed.
567 file.mtime = 0;
568 file.inode = 0;
569 }
570 }
571
572 /// Returns true if the stat was changed. Updates the `file` with the new stat value.
573 fn setStatChanged(file: *File, m: *Manifest, s: Stat) Io.Cancelable!bool {
574 if (s.size == file.size and
575 s.mtime.nanoseconds == file.mtime and
576 s.inode == file.inode)
577 {
578 return false;
579 } else {
580 try setStat(file, m, s);
581 return true;
582 }
583 }
584
585 /// `path_len` does not include the null byte.
586 pub fn sizeOf(path_len: usize) usize {
587 const end = @offsetOf(File, "path_start") + path_len;
588 const needed_alignment = @alignOf(File) - (end % @alignOf(File));
589 assert(needed_alignment >= 1); // Always need at least a null byte.
590 return end + needed_alignment;
591 }
592 };
593
594 pub const CheckDiagnostic = union(enum) {
347595 manifest_create: Io.File.OpenError,
348 manifest_read: Io.File.Reader.Error,
596 manifest_stat: Io.File.StatError,
597 manifest_oversize,
598 manifest_read: Io.File.ReadPositionalError,
349599 manifest_lock: Io.File.LockError,
350600 file_open: FileOp,
351601 file_stat: FileOp,
......@@ -353,115 +603,274 @@ pub const Manifest = struct {
353603 file_hash: FileOp,
354604
355605 pub const FileOp = struct {
356 file_index: usize,
606 file_offset: File.Offset,
357607 err: anyerror,
608
609 /// Returned `Path` references `Manifest.contents`.
610 pub fn path(fo: FileOp, manifest: *const Manifest) Path {
611 const contents = manifest.contents.items;
612 const prefix = fo.file_offset.get(contents).flags.prefix;
613 return .{
614 .root_dir = manifest.cache.prefixes()[prefix],
615 .sub_path = fo.file_offset.path(contents),
616 };
617 }
358618 };
359 };
360619
361 pub const Files = std.array_hash_map.Custom(File, void, FilesContext, false);
620 pub const Format = struct {
621 diagnostic: CheckDiagnostic,
622 manifest: *const Manifest,
623
624 pub fn format(this: @This(), w: *Io.Writer) Io.Writer.Error!void {
625 switch (this.diagnostic) {
626 .manifest_oversize => return w.writeAll(@tagName(this.diagnostic)),
627 .manifest_create, .manifest_stat, .manifest_read, .manifest_lock => |e| {
628 return w.print("{t} {t}", .{ this.diagnostic, e });
629 },
630 .file_open, .file_stat, .file_read, .file_hash => |op| {
631 const path = op.path(this.manifest);
632 return w.print("{t} {t} {f}", .{ this.diagnostic, op.err, path });
633 },
634 }
635 }
636 };
362637
363 pub const FilesContext = struct {
364 pub fn hash(fc: FilesContext, file: File) u32 {
365 _ = fc;
366 return file.prefixed_path.hash();
638 pub fn fmt(diagnostic: CheckDiagnostic, manifest: *const Manifest) Format {
639 return .{
640 .diagnostic = diagnostic,
641 .manifest = manifest,
642 };
367643 }
644 };
645
646 pub const Stat = struct {
647 size: u64,
648 inode: Io.File.INode,
649 mtime: Io.Timestamp,
368650
369 pub fn eql(fc: FilesContext, a: File, b: File, b_index: usize) bool {
370 _ = fc;
371 _ = b_index;
372 return a.prefixed_path.eql(b.prefixed_path);
651 pub fn init(other: Io.File.Stat) Stat {
652 return .{
653 .size = other.size,
654 .inode = other.inode,
655 .mtime = other.mtime,
656 };
373657 }
374658 };
375659
376 const FilesAdapter = struct {
377 pub fn eql(context: @This(), a: PrefixedPath, b: File, b_index: usize) bool {
378 _ = context;
379 _ = b_index;
380 return a.eql(b.prefixed_path);
660 pub const PathHandle = union(enum) {
661 file: ?Io.File,
662 /// If provided, this handle must be opened with iteration capability.
663 dir: ?Io.Dir,
664
665 pub fn isDirectory(this: @This()) bool {
666 return this == .dir;
381667 }
382668
383 pub fn hash(context: @This(), key: PrefixedPath) u32 {
384 _ = context;
385 return key.hash();
669 pub fn have(this: @This()) bool {
670 return switch (this) {
671 .file => |opt_file| opt_file != null,
672 .dir => |opt_dir| opt_dir != null,
673 };
386674 }
387675 };
388676
389 /// Add a file as a dependency of process being cached. When `hit` is
390 /// called, the file's contents will be checked to ensure that it matches
391 /// the contents from previous times.
677 pub const AddInputPathOptions = struct {
678 /// If provided, will be closed when `check` is called, unless `request_handle` is also set.
679 handle: PathHandle = .{ .file = null },
680 stat: ?Stat = null,
681 /// If set, file handle will remain open after `check` is called.
682 request_handle: bool = false,
683 /// Can request file or directory contents depending on `handle`.
684 request_contents: bool = false,
685 /// Content hashing skipped; any difference in metadata implies cache
686 /// miss.
687 metadata_only: bool = false,
688 };
689
690 /// Add a file or directory path as a dependency of process being cached.
691 /// When `hit` is called, the contents will be checked to ensure
692 /// that it matches the contents from previous times.
392693 ///
393 /// Max file size will be used to determine the amount of space the file contents
394 /// are allowed to take up in memory. If max_file_size is null, then the contents
395 /// will not be loaded into memory.
694 /// The contents of the input file may be requested and subsequently
695 /// obtained via methods of the returned `InputPath.Index` after calling
696 /// `hit`.
396697 ///
397 /// Returns the index of the entry in the `files` array list. You can use it
398 /// to access the contents of the file after calling `hit()` like so:
698 /// Contents of a directory are considered to be the sorted list of file
699 /// names of direct entries, separated by null byte. Each file name is
700 /// prefixed by `Io.File.Kind` byte, +1 so that the zero tag is not aliased
701 /// by the entry separator.
399702 ///
400 /// ```
401 /// var file_contents = cache_hash.files.keys()[file_index].contents.?;
402 /// ```
403 pub fn addFilePath(m: *Manifest, file_path: Path, max_file_size: ?usize) !usize {
404 return addOpenedFile(m, file_path, null, max_file_size);
703 /// See also:
704 /// * `addDiscoveredPath`
705 pub fn addInputPath(m: *Manifest, path: Path, options: AddInputPathOptions) Allocator.Error!InputPath.Index {
706 assert(m.state == .input);
707 const cache = m.cache;
708 const gpa = cache.gpa;
709 const is_directory = options.handle.isDirectory();
710
711 try m.files.ensureUnusedCapacityContext(gpa, 1, .{ .contents = m.contents.items });
712 try m.input_paths.ensureUnusedCapacity(gpa, 1);
713
714 const new_file_offset: File.Offset = @fromBackingInt(@intCast(m.contents.items.len));
715 try m.contents.appendNTimes(gpa, 0, @offsetOf(File, "path_start"));
716 errdefer m.contents.shrinkRetainingCapacity(@backingInt(new_file_offset));
717
718 const new_prefix = try cache.resolveAppendPath(&m.contents, path);
719 assert(mem.isAligned(m.contents.items.len, @alignOf(File)));
720 new_file_offset.get(m.contents.items).flags = .{
721 .prefix = new_prefix,
722 .is_directory = is_directory,
723 .metadata_only = options.metadata_only,
724 };
725
726 const gop = m.files.getOrPutAssumeCapacityContext(new_file_offset, .{ .contents = m.contents.items });
727 m.files.lockPointers();
728 defer m.files.unlockPointers();
729
730 if (gop.found_existing) {
731 m.contents.shrinkRetainingCapacity(@backingInt(new_file_offset));
732 const existing_input_file = &m.input_paths.items[gop.index];
733 switch (options.handle) {
734 .file => |opt_file| if (opt_file) |file| {
735 existing_input_file.handle = .{ .file = file };
736 existing_input_file.have_handle = true;
737 },
738 .dir => |opt_dir| if (opt_dir) |dir| {
739 existing_input_file.handle = .{ .dir = dir };
740 existing_input_file.have_handle = true;
741 },
742 }
743 if (options.request_contents) switch (existing_input_file.contents.unwrap()) {
744 .requested, .unrequested => existing_input_file.contents = .requested,
745 .populated => {},
746 };
747 const existing_header = m.files.keys()[gop.index].get(m.contents.items);
748 if (options.stat) |stat| {
749 existing_input_file.have_stat = true;
750 existing_header.size = stat.size;
751 existing_header.inode = stat.inode;
752 existing_header.mtime = @intCast(stat.mtime.toNanoseconds());
753 }
754 // If it trips, the same file path has been added to the cache
755 // manifest both as a directory and as a normal file, making the
756 // intended caching behavior ambiguous.
757 assert(existing_header.flags.is_directory == is_directory);
758 if (!options.metadata_only)
759 existing_header.flags.metadata_only = false;
760 } else {
761 m.input_paths.appendAssumeCapacity(.{
762 .request_handle = options.request_handle,
763 .have_handle = options.handle.have(),
764 .handle = switch (options.handle) {
765 .file => |opt_file| if (opt_file) |file| .{ .file = file } else undefined,
766 .dir => |opt_dir| if (opt_dir) |dir| .{ .dir = dir } else undefined,
767 },
768 .contents = if (options.request_contents) .requested else .unrequested,
769 .have_digest = false,
770 .have_stat = options.stat != null,
771 });
772 assert(m.input_paths.items.len - 1 == gop.index);
773 if (options.stat) |stat| {
774 const header = new_file_offset.get(m.contents.items);
775 header.size = stat.size;
776 header.inode = stat.inode;
777 header.mtime = @intCast(stat.mtime.toNanoseconds());
778 }
779 }
780 return @fromBackingInt(@intCast(gop.index));
405781 }
406782
407 /// Same as `addFilePath` except the file has already been opened.
408 pub fn addOpenedFile(m: *Manifest, path: Path, handle: ?Io.File, max_file_size: ?usize) !usize {
409 const gpa = m.cache.gpa;
410 try m.files.ensureUnusedCapacity(gpa, 1);
411 const resolved_path = try std.fs.path.resolve(gpa, &.{
412 path.root_dir.path orelse ".",
413 path.subPathOrDot(),
414 });
415 errdefer gpa.free(resolved_path);
416 const prefixed_path = try m.cache.findPrefixResolved(resolved_path);
417 return addFileInner(m, prefixed_path, handle, max_file_size);
783 pub fn addInputPathOptional(m: *Manifest, opt_path: ?Path, options: AddInputPathOptions) Allocator.Error!void {
784 assert(m.state == .input);
785 m.hash.add(opt_path != null);
786 _ = try addInputPath(m, opt_path orelse return, options);
418787 }
419788
420 fn addFileInner(self: *Manifest, prefixed_path: PrefixedPath, handle: ?Io.File, max_file_size: ?usize) usize {
421 const gop = self.files.getOrPutAssumeCapacityAdapted(prefixed_path, FilesAdapter{});
422 if (gop.found_existing) {
423 self.cache.gpa.free(prefixed_path.sub_path);
424 gop.key_ptr.updateMaxSize(max_file_size);
425 gop.key_ptr.updateHandle(handle);
426 return gop.index;
427 }
428 gop.key_ptr.* = .{
429 .prefixed_path = prefixed_path,
430 .contents = null,
431 .max_file_size = max_file_size,
432 .stat = undefined,
433 .bin_digest = undefined,
434 .handle = handle,
789 pub const AddInputDepFileError = error{
790 InvalidDepFile,
791 } || Allocator.Error || Io.File.OpenError || Io.File.Reader.Error;
792
793 pub fn addInputDepFile(m: *Manifest, path: Path, diagnostic: ?*DepTokenizer.Token) AddInputDepFileError!void {
794 assert(m.manifest_file == null);
795 assert(m.state == .input);
796
797 const cache = m.cache;
798 const gpa = cache.gpa;
799 const io = cache.io;
800
801 // TODO: change DepTokenizer to be streaming rather than operating on slice of bytes.
802 const prev_len = m.all_input_content.items.len;
803 defer m.all_input_content.items.len = prev_len;
804
805 const file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
806 defer file.close(io);
807
808 var file_reader: Io.File.Reader = .init(file, io, &.{});
809 file_reader.interface.appendRemainingUnlimited(gpa, &m.all_input_content) catch |err| switch (err) {
810 error.OutOfMemory => |e| return e,
811 error.ReadFailed => return file_reader.err.?,
435812 };
436813
437 self.hash.add(prefixed_path.prefix);
438 self.hash.addBytes(prefixed_path.sub_path);
814 var aux: std.ArrayList(u8) = .empty;
815 defer aux.deinit(gpa);
439816
440 return gop.index;
817 var it: DepTokenizer = .{ .bytes = m.all_input_content.items[prev_len..] };
818 while (it.next()) |token| switch (token) {
819 .target, .target_must_resolve => {},
820 .prereq => |file_path| {
821 _ = try m.addInputPath(.initCwd(file_path), .{});
822 },
823 .prereq_must_resolve => {
824 aux.clearRetainingCapacity();
825 try token.resolve(gpa, &aux);
826 _ = try m.addInputPath(.initCwd(aux.items), .{});
827 },
828 else => |err| {
829 if (diagnostic) |d| d.* = err;
830 return error.InvalidDepFile;
831 },
832 };
441833 }
442834
443 pub fn addOptionalFilePath(self: *Manifest, optional_file_path: ?Path) !void {
444 self.hash.add(optional_file_path != null);
445 const file_path = optional_file_path orelse return;
446 _ = try self.addFilePath(file_path, null);
447 }
835 pub const CheckResult = union(enum) {
836 hit,
837 incomplete_manifest,
838 invalid_manifest,
839 path_deleted: File.Offset,
840 directory_status_changed: File.Offset,
841 metadata_changed: File.Offset,
842 contents_changed: File.Offset,
843
844 pub const Format = struct {
845 check_result: CheckResult,
846 manifest: *const Manifest,
847
848 pub fn format(this: @This(), w: *Io.Writer) Io.Writer.Error!void {
849 switch (this.check_result) {
850 .hit, .incomplete_manifest, .invalid_manifest => return w.writeAll(@tagName(this.check_result)),
851 .path_deleted, .directory_status_changed, .metadata_changed, .contents_changed => |off| {
852 const path = off.path(this.manifest.contents.items);
853 return w.print("{t} {s}", .{ this.check_result, path });
854 },
855 }
856 }
857 };
448858
449 pub fn addDepFile(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void {
450 assert(self.manifest_file == null);
451 return self.addDepFileMaybePost(dir, dep_file_sub_path);
452 }
859 pub fn fmt(check_result: CheckResult, manifest: *const Manifest) Format {
860 return .{
861 .check_result = check_result,
862 .manifest = manifest,
863 };
864 }
865 };
453866
454 pub const HitError = error{
867 pub const CheckError = error{
455868 /// Unable to check the cache for a reason that has been recorded into
456869 /// the `diagnostic` field.
457870 CacheCheckFailed,
458 /// A cache manifest file exists however it could not be parsed.
459 InvalidFormat,
460 OutOfMemory,
461 Canceled,
462 };
871 } || Allocator.Error || Io.Cancelable;
463872
464 /// Check the cache to see if the input exists in it. If it exists, returns `true`.
873 /// Check the cache to see if the input exists in it.
465874 /// A hex encoding of its hash is available by calling `final`.
466875 ///
467876 /// This function will also acquire an exclusive lock to the manifest file. This means
......@@ -473,52 +882,53 @@ pub const Manifest = struct {
473882 /// The lock on the manifest file is released when `deinit` is called. As another
474883 /// option, one may call `toOwnedLock` to obtain a smaller object which can represent
475884 /// the lock. `deinit` is safe to call whether or not `toOwnedLock` has been called.
476 pub fn hit(man: *Manifest, parent_progress_node: std.Progress.Node) HitError!bool {
885 pub fn check(
886 man: *Manifest,
887 diag: *CheckDiagnostic,
888 parent_progress_node: std.Progress.Node,
889 ) CheckError!CheckResult {
477890 const node = parent_progress_node.start("Reusing Cache Artifacts", 0);
478891 defer node.end();
479 return hitInner(man);
892 return checkProgressless(man, diag);
480893 }
481894
482 pub fn hitInner(self: *Manifest) HitError!bool {
483 assert(self.manifest_file == null);
484
485 self.diagnostic = .none;
486
487 const ext = ".txt";
488 var manifest_file_path: [hex_digest_len + ext.len]u8 = undefined;
489
490 var bin_digest: BinDigest = undefined;
491 self.hash.hasher.final(&bin_digest);
895 pub fn checkProgressless(man: *Manifest, diag: *CheckDiagnostic) CheckError!CheckResult {
896 assert(man.state == .input);
897 assert(man.manifest_file == null);
492898
493 self.hex_digest = binToHex(bin_digest);
899 // This is *not* hashing the contents of the input files. It is the
900 // flags (including prefix) and path only.
901 for (man.files.keys()[0..man.input_paths.items.len]) |file_off| {
902 const flags_and_path = file_off.flagsAndPath(man.contents.items);
903 man.hash.hasher.update(mem.absorbSentinel(flags_and_path));
904 }
494905
495 @memcpy(manifest_file_path[0..self.hex_digest.len], &self.hex_digest);
496 manifest_file_path[hex_digest_len..][0..ext.len].* = ext.*;
906 var input_digest: BinDigest = undefined;
907 man.hash.hasher.final(&input_digest);
908 const input_hex_digest = binToHex(input_digest);
497909
498 const io = self.cache.io;
910 const manifest_file_path = &input_hex_digest;
911 const io = man.cache.io;
499912
500913 // We'll try to open the cache with an exclusive lock, but if that would block
501914 // and `want_shared_lock` is set, a shared lock might be sufficient, so we'll
502915 // open with a shared lock instead.
503916 while (true) {
504 if (self.cache.manifest_dir.createFile(io, &manifest_file_path, .{
917 if (man.cache.manifest_dir.createFile(io, manifest_file_path, .{
505918 .read = true,
506919 .truncate = false,
507920 .lock = .exclusive,
508 .lock_nonblocking = self.want_shared_lock,
921 .lock_nonblocking = man.want_shared_lock,
509922 })) |manifest_file| {
510 self.manifest_file = manifest_file;
511 self.have_exclusive_lock = true;
923 man.manifest_file = manifest_file;
924 man.have_exclusive_lock = true;
512925 break;
513926 } else |err| switch (err) {
514927 error.WouldBlock => {
515 self.manifest_file = self.cache.manifest_dir.openFile(io, &manifest_file_path, .{
928 man.manifest_file = man.cache.manifest_dir.openFile(io, manifest_file_path, .{
516929 .mode = .read_write,
517930 .lock = .shared,
518 }) catch |e| {
519 self.diagnostic = .{ .manifest_create = e };
520 return error.CacheCheckFailed;
521 };
931 }) catch |e| return fail(diag, .{ .manifest_create = e });
522932 break;
523933 },
524934 error.FileNotFound => {
......@@ -531,317 +941,699 @@ pub const Manifest = struct {
531941 // disambiguates by returning EEXIST, indicating original
532942 // failure was a race, or ENOENT, indicating deletion of
533943 // the directory of our open handle.
534 if (!builtin.os.tag.isDarwin()) {
535 self.diagnostic = .{ .manifest_create = error.FileNotFound };
536 return error.CacheCheckFailed;
537 }
944 if (!builtin.os.tag.isDarwin()) return fail(diag, .{ .manifest_create = error.FileNotFound });
538945
539 if (self.cache.manifest_dir.createFile(io, &manifest_file_path, .{
946 if (man.cache.manifest_dir.createFile(io, manifest_file_path, .{
540947 .read = true,
541948 .truncate = false,
542949 .lock = .exclusive,
543 .lock_nonblocking = self.want_shared_lock,
950 .lock_nonblocking = man.want_shared_lock,
544951 .exclusive = true,
545952 })) |manifest_file| {
546 self.manifest_file = manifest_file;
547 self.have_exclusive_lock = true;
953 man.manifest_file = manifest_file;
954 man.have_exclusive_lock = true;
548955 break;
549956 } else |excl_err| switch (excl_err) {
550957 error.WouldBlock, error.PathAlreadyExists => continue,
551 error.FileNotFound => {
552 self.diagnostic = .{ .manifest_create = error.FileNotFound };
553 return error.CacheCheckFailed;
554 },
958 error.FileNotFound => return fail(diag, .{ .manifest_create = error.FileNotFound }),
555959 error.Canceled => |e| return e,
556 else => |e| {
557 self.diagnostic = .{ .manifest_create = e };
558 return error.CacheCheckFailed;
559 },
960 else => |e| return fail(diag, .{ .manifest_create = e }),
560961 }
561962 },
562 error.Canceled => |e| return e,
563 else => |e| {
564 self.diagnostic = .{ .manifest_create = e };
565 return error.CacheCheckFailed;
963 error.Canceled => |e| return e,
964 else => |e| return fail(diag, .{ .manifest_create = e }),
965 }
966 }
967
968 man.want_refresh_timestamp = true;
969
970 // We're going to construct a second hash. Its input will begin with the digest we've already computed
971 // (`input_digest`), and then it'll have the digests of each input file, including discovered files (see
972 // `addDiscoveredPath`). If this is a hit, we learn the set of discovered files from the manifest on disk. If
973 // this is a miss, we'll learn those from future calls to `addDiscoveredPath` etc. As such, the state of
974 // `man.hash.hasher` after this function depends on whether this is a hit or a miss.
975 //
976 // If we return `CacheStatus.hit`, then `man.hash.hasher` must already include the digests of the discovered
977 // files, so the caller can call `final`. Otherwise, on a cache miss, `man.hash.hasher` will include the digests
978 // of all non-discovered files -- that is, the ones we've already been told about. The rest will be discovered
979 // through calls to `addDiscoveredPath` etc, which will update the hasher. After all files are added, the user
980 // can use `final`, and will at some point `finalize` the file list to disk.
981 man.hash.hasher = hasher_init;
982 man.hash.hasher.update(&input_digest);
983
984 hit: {
985 const miss_result = miss: {
986 const result = try man.checkLocked(diag);
987 if (result == .hit) {
988 break :hit;
989 } else if (!try man.upgradeToExclusiveLock(diag)) {
990 break :miss result;
991 }
992 // Missed with the shared lock, and upgraded to an exclusive lock. However, another process may have
993 // modified the cache directory, so we need to check again before deciding to miss.
994 man.shrinkFilesToInput();
995 const refreshed_result = try man.checkLocked(diag);
996 if (refreshed_result == .hit) break :hit;
997 break :miss refreshed_result;
998 };
999
1000 // Cache miss, but `checkLocked` guarantees that all input files have their digests computed, even on a
1001 // cache miss, which is needed because they will be used in the manifest digest.
1002 man.state = .miss;
1003 return miss_result;
1004 }
1005
1006 if (man.want_shared_lock) {
1007 man.downgradeToSharedLock() catch |err| return fail(diag, .{ .manifest_lock = err });
1008 }
1009
1010 man.state = .hit;
1011 return .hit;
1012 }
1013
1014 fn transitionToMissDiscovered(m: *Manifest) void {
1015 switch (m.state) {
1016 .input => unreachable,
1017 .hit => unreachable,
1018 .hit_digested => unreachable,
1019 .miss => {
1020 m.shrinkFilesToInput();
1021 m.state = .miss_discovered;
1022 },
1023 .miss_discovered => {},
1024 .miss_digested => unreachable,
1025 .miss_finalized => unreachable,
1026 }
1027 }
1028
1029 fn shrinkFilesToInput(m: *Manifest) void {
1030 if (m.files.count() <= m.input_paths.items.len) return;
1031 // Reads from files hash map whose data is destroyed on the next line.
1032 const off = m.files.keys()[m.input_paths.items.len];
1033 // Reads from the unshrunken contents whose data is destroyed on the next line.
1034 m.files.shrinkRetainingCapacityContext(m.input_paths.items.len, .{ .contents = m.contents.items });
1035 m.contents.shrinkRetainingCapacity(@backingInt(off));
1036 assert(mem.isAligned(m.contents.items.len, @alignOf(File)));
1037 }
1038
1039 /// Does not observe or modify `self.hash.hasher`. Asserts `self.files` contains only the original input files.
1040 fn checkLocked(m: *Manifest, diag: *CheckDiagnostic) CheckError!CheckResult {
1041 assert(m.files.count() == m.input_paths.items.len);
1042 const gpa = m.cache.gpa;
1043 const io = m.cache.io;
1044 const manifest_file = m.manifest_file.?;
1045
1046 const manifest_stat = manifest_file.stat(io) catch |err| switch (err) {
1047 error.Canceled => |e| return e,
1048 else => |e| return fail(diag, .{ .manifest_stat = e }),
1049 };
1050 const manifest_size = std.math.cast(u32, manifest_stat.size) orelse
1051 return fail(diag, .manifest_oversize);
1052
1053 if (manifest_size == 0 or manifest_size < m.contents.items.len) {
1054 // Manifest file was never finalized.
1055 try m.contents.ensureUnusedCapacity(gpa, 1);
1056 return populateMissingInputFileHashes(m, diag, 0, .incomplete_manifest);
1057 }
1058
1059 // We must not clobber existing `Manifest.contents` because it possibly contains prepopulated stat and digest
1060 // information which we must compare with the file system. Since non input files are typically greater in number
1061 // than input files, the strategy here is to move the contents to make room for entirety of the manifest
1062 // contents on disk, and then later truncate the contents array.
1063 //
1064 // The file on disk already supposedly includes the extra null byte that we have to maintain.
1065 const input_contents_len = m.contents.items.len;
1066 // After this resize, every return statement needs to modify m.contents length.
1067 try m.contents.resize(gpa, input_contents_len + manifest_size);
1068 errdefer m.contents.shrinkRetainingCapacity(input_contents_len);
1069 @memcpy(m.contents.items[manifest_size..][0..input_contents_len], m.contents.items[0..input_contents_len]);
1070 {
1071 const n = manifest_file.readPositionalAll(io, m.contents.items, 0) catch |err| switch (err) {
1072 error.Canceled => |e| return e,
1073 else => |e| return fail(diag, .{ .manifest_read = e }),
1074 };
1075 if (n != manifest_size) return missInput(m, diag, 0, input_contents_len, manifest_size, .incomplete_manifest);
1076 }
1077 const disk_contents = m.contents.items[0..manifest_size];
1078 const input_contents = m.contents.items[manifest_size..][0..input_contents_len];
1079
1080 // First the input files section, which must match our input files, otherwise it's invalid or incomplete.
1081 var off: usize = 0;
1082 for (m.input_paths.items, m.files.keys()[0..m.input_paths.items.len], 0..) |*input_path, file_off, i| {
1083 if (@backingInt(file_off) + 1 >= disk_contents.len)
1084 return missInput(m, diag, i, input_contents_len, manifest_size, .incomplete_manifest);
1085 if (!mem.eql(u8, file_off.flagsAndPath(disk_contents), file_off.flagsAndPath(input_contents)))
1086 return missInput(m, diag, i, input_contents_len, manifest_size, .invalid_manifest);
1087
1088 const result = try checkInputPath(m, diag, file_off, input_path, disk_contents, input_contents);
1089 if (result != .hit) return missInput(m, diag, i + 1, input_contents_len, manifest_size, result);
1090 off = @backingInt(file_off);
1091 }
1092
1093 // Guess number of files based on manifest contents len to reduce allocations.
1094 // This is not an upper bound; subsequent insertions may potentially allocate.
1095 try m.files.ensureUnusedCapacityContext(gpa, disk_contents.len / (@sizeOf(File) + 32), .{
1096 .contents = disk_contents,
1097 });
1098
1099 // Validate and check discovered files.
1100 while (off + 1 < disk_contents.len) {
1101 const file_off: File.Offset = @fromBackingInt(@intCast(off));
1102 const file = file_off.getFallible(disk_contents) catch return .invalid_manifest;
1103 if (file.flags.prefix >= m.cache.prefixes_len) return .invalid_manifest;
1104 const path = file_off.pathFallible(disk_contents) catch return .invalid_manifest;
1105 if (path.len == 0) return .invalid_manifest;
1106
1107 try m.files.putContext(gpa, file_off, {}, .{ .contents = disk_contents });
1108 const result = try checkDiscoveredPath(m, diag, file_off, disk_contents);
1109 if (result != .hit) return result;
1110
1111 off += File.sizeOf(path.len);
1112 }
1113
1114 // Final terminating zero byte to distinguish empty manifest file from
1115 // manifest with zero files.
1116 const file_valid = off + 1 == disk_contents.len and disk_contents[off] == 0;
1117 if (!file_valid) return .incomplete_manifest;
1118
1119 // Since it's a cache hit, we accept the input file contents from disk and discard the other copy.
1120 // Furthermore, don't track the trailing zero byte in contents.
1121 m.contents.shrinkRetainingCapacity(manifest_size - 1);
1122 return .hit;
1123 }
1124
1125 /// Restore input file contents to prepare for cache miss workflow. We want to keep everything prior to `off` to
1126 /// avoid redundant computation of digest and stat.
1127 ///
1128 /// Furthermore, this function is responsible for populating the remaining input file digests and metadata after the
1129 /// missed one.
1130 fn missInput(
1131 m: *Manifest,
1132 diag: *CheckDiagnostic,
1133 next_file_index: usize,
1134 input_contents_len: usize,
1135 manifest_size: usize,
1136 result: CheckResult,
1137 ) CheckError!CheckResult {
1138 const file_offs = m.files.keys();
1139 const off = if (file_offs.len - next_file_index == 0)
1140 input_contents_len
1141 else
1142 @backingInt(file_offs[next_file_index]);
1143 const copy_len = input_contents_len - off;
1144 if (manifest_size > 0) @memcpy(
1145 m.contents.items[off..][0..copy_len],
1146 m.contents.items[manifest_size + off ..][0..copy_len],
1147 );
1148 m.contents.shrinkRetainingCapacity(input_contents_len);
1149 return populateMissingInputFileHashes(m, diag, next_file_index, result);
1150 }
1151
1152 fn populateMissingInputFileHashes(
1153 m: *Manifest,
1154 diag: *CheckDiagnostic,
1155 next_file_index: usize,
1156 result: CheckResult,
1157 ) CheckError!CheckResult {
1158 const file_offs = m.files.keys();
1159 const contents = m.contents.items;
1160 for (file_offs[next_file_index..], m.input_paths.items[next_file_index..]) |input_file_off, *input_path| {
1161 try populateInputPath(m, diag, input_file_off, input_path, contents);
1162 }
1163 return result;
1164 }
1165
1166 /// Similar to `checkInputPath` and `checkDiscoveredPath` but does not compare metadata or digest against file
1167 /// system. Only ensures that metadata and digest are available.
1168 fn populateInputPath(
1169 m: *Manifest,
1170 diag: *CheckDiagnostic,
1171 file_off: File.Offset,
1172 input_path: *InputPath,
1173 contents: []u8,
1174 ) CheckError!void {
1175 const cache = m.cache;
1176 const io = cache.io;
1177 const input_file = file_off.get(contents);
1178 const parent_dir = cache.prefixes()[input_file.flags.prefix].handle;
1179 const file_path = file_off.path(contents);
1180 const gpa = cache.gpa;
1181
1182 if (input_path.have_digest) return;
1183
1184 const stat_path_ok = switch (input_path.contents.unwrap()) {
1185 .populated => true,
1186 .unrequested, .requested => input_file.flags.metadata_only,
1187 };
1188 if (!input_path.have_stat and stat_path_ok) {
1189 // Since this is an input file, FileNotFound counts as a failure, not a cache miss.
1190 const actual_stat = switch (input_path.getHandle(input_file)) {
1191 .none => parent_dir.statFile(io, file_path, .{}) catch |err| switch (err) {
1192 error.Canceled => |e| return e,
1193 else => |e| return fail(diag, .{ .file_stat = .{
1194 .file_offset = file_off,
1195 .err = e,
1196 } }),
1197 },
1198 .file => |opened_file| opened_file.stat(io) catch |err| switch (err) {
1199 error.Canceled => |e| return e,
1200 else => |e| return fail(diag, .{ .file_stat = .{
1201 .file_offset = file_off,
1202 .err = e,
1203 } }),
1204 },
1205 .dir => |opened_dir| opened_dir.stat(io) catch |err| switch (err) {
1206 error.Canceled => |e| return e,
1207 else => |e| return fail(diag, .{ .file_stat = .{
1208 .file_offset = file_off,
1209 .err = e,
1210 } }),
1211 },
1212 };
1213 const actual_is_directory = actual_stat.kind == .directory;
1214 if (actual_is_directory != input_file.flags.is_directory) {
1215 // Since this is an input file, this is a failure, not a cache miss.
1216 return fail(diag, .{ .file_stat = .{
1217 .file_offset = file_off,
1218 .err = if (actual_is_directory) error.IsDir else error.NotDir,
1219 } });
1220 }
1221 try input_file.setStat(m, .init(actual_stat));
1222 }
1223
1224 if (input_file.flags.metadata_only) {
1225 assert(stat_path_ok);
1226 input_file.hashFromMetadata();
1227 return;
1228 }
1229
1230 if (input_path.contents.get(m.all_input_content.items)) |file_or_dir_contents| {
1231 assert(stat_path_ok);
1232 var hasher = hasher_init;
1233 hasher.update(file_or_dir_contents);
1234 hasher.final(&input_file.digest);
1235 return;
1236 }
1237
1238 if (input_file.flags.is_directory) {
1239 const opened_dir = if (input_path.have_handle)
1240 input_path.handle.dir
1241 else
1242 parent_dir.openDir(io, file_path, .{
1243 .iterate = true,
1244 .access_sub_paths = false,
1245 }) catch |err| switch (err) {
1246 error.Canceled => |e| return e,
1247 else => |e| return fail(diag, .{ .file_open = .{
1248 .file_offset = file_off,
1249 .err = e,
1250 } }),
1251 };
1252 defer if (!input_path.request_handle) opened_dir.close(io) else {
1253 input_path.handle = .{ .dir = opened_dir };
1254 input_path.have_handle = true;
1255 };
1256
1257 if (!input_path.have_stat) {
1258 assert(!stat_path_ok);
1259 const actual_stat = opened_dir.stat(io) catch |err| switch (err) {
1260 error.Canceled => |e| return e,
1261 else => |e| return fail(diag, .{ .file_stat = .{
1262 .file_offset = file_off,
1263 .err = e,
1264 } }),
1265 };
1266 try input_file.setStat(m, .init(actual_stat));
1267 }
1268
1269 const dir_contents_start = m.all_input_content.items.len;
1270 hashDir(gpa, io, opened_dir, &input_file.digest, &m.all_input_content) catch |err| switch (err) {
1271 error.Canceled, error.OutOfMemory => |e| return e,
1272 else => |e| return fail(diag, .{ .file_read = .{
1273 .file_offset = file_off,
1274 .err = e,
1275 } }),
1276 };
1277 defer switch (input_path.contents.unwrap()) {
1278 .requested => input_path.contents = .populated(.{
1279 .off = @intCast(dir_contents_start),
1280 .len = @intCast(m.all_input_content.items.len - dir_contents_start),
1281 }),
1282 .unrequested => m.all_input_content.shrinkRetainingCapacity(dir_contents_start),
1283 .populated => unreachable,
1284 };
1285 } else {
1286 const opened_file = if (input_path.have_handle)
1287 input_path.handle.file
1288 else
1289 parent_dir.openFile(io, file_path, .{ .mode = .read_only }) catch |err| switch (err) {
1290 error.Canceled => |e| return e,
1291 else => |e| return fail(diag, .{ .file_open = .{
1292 .file_offset = file_off,
1293 .err = e,
1294 } }),
1295 };
1296 defer if (!input_path.request_handle) opened_file.close(io) else {
1297 input_path.handle = .{ .file = opened_file };
1298 input_path.have_handle = true;
1299 };
1300
1301 if (!input_path.have_stat) {
1302 assert(!stat_path_ok);
1303 const actual_stat = opened_file.stat(io) catch |err| switch (err) {
1304 error.Canceled => |e| return e,
1305 else => |e| return fail(diag, .{ .file_stat = .{
1306 .file_offset = file_off,
1307 .err = e,
1308 } }),
1309 };
1310 try input_file.setStat(m, .init(actual_stat));
1311 }
1312
1313 switch (input_path.contents.unwrap()) {
1314 .requested => {
1315 const start = m.all_input_content.items.len;
1316 hashFileAppend(io, opened_file, &input_file.digest, &m.all_input_content, gpa) catch |err| switch (err) {
1317 error.Canceled, error.OutOfMemory => |e| return e,
1318 else => |e| return fail(diag, .{ .file_read = .{
1319 .file_offset = file_off,
1320 .err = e,
1321 } }),
1322 };
1323 input_path.contents = .populated(.{
1324 .off = @intCast(start),
1325 .len = @intCast(m.all_input_content.items.len - start),
1326 });
1327 },
1328 .unrequested => hashFile(io, opened_file, &input_file.digest) catch |err| switch (err) {
1329 error.Canceled => |e| return e,
1330 else => |e| return fail(diag, .{ .file_read = .{
1331 .file_offset = file_off,
1332 .err = e,
1333 } }),
5661334 },
1335 .populated => unreachable,
5671336 }
5681337 }
1338 }
5691339
570 self.want_refresh_timestamp = true;
571
572 const input_file_count = self.files.entries.len;
573
574 // We're going to construct a second hash. Its input will begin with the digest we've
575 // already computed (`bin_digest`), and then it'll have the digests of each input file,
576 // including "post" files (see `addFilePost`). If this is a hit, we learn the set of "post"
577 // files from the manifest on disk. If this is a miss, we'll learn those from future calls
578 // to `addFilePost` etc. As such, the state of `self.hash.hasher` after this function
579 // depends on whether this is a hit or a miss.
580 //
581 // If we return `true` indicating a cache hit, then `self.hash.hasher` must already include
582 // the digests of the "post" files, so the caller can call `final`. Otherwise, on a cache
583 // miss, `self.hash.hasher` will include the digests of all non-"post" files -- that is,
584 // the ones we've already been told about. The rest will be discovered through calls to
585 // `addFilePost` etc, which will update the hasher. After all files are added, the user can
586 // use `final`, and will at some point `writeManifest` the file list to disk.
1340 /// Upon return, disk_contents digest and stat will be populated regardless of whether hit or miss occurs.
1341 fn checkInputPath(
1342 m: *Manifest,
1343 diag: *CheckDiagnostic,
1344 file_off: File.Offset,
1345 input_path: *InputPath,
1346 disk_contents: []u8,
1347 input_contents: []u8,
1348 ) CheckError!CheckResult {
1349 const cache = m.cache;
1350 const io = cache.io;
1351 const gpa = cache.gpa;
1352 const disk_file = file_off.get(disk_contents);
1353 const input_file = file_off.get(input_contents);
1354 const parent_dir = cache.prefixes()[disk_file.flags.prefix].handle;
1355 const file_path = file_off.path(disk_contents);
1356
1357 assert(disk_file.flags == input_file.flags);
1358
1359 if (input_path.have_digest) {
1360 if (mem.eql(u8, &disk_file.digest, &input_file.digest))
1361 return .hit;
1362 }
5871363
588 self.hash.hasher = hasher_init;
589 self.hash.hasher.update(&bin_digest);
1364 if (input_path.have_stat) {
1365 const changed = try disk_file.setStatChanged(m, input_file.stat());
1366 if (!changed) return .hit;
1367 }
5901368
591 hit: {
592 const file_digests_populated: usize = digests: {
593 switch (try self.hitWithCurrentLock()) {
594 .hit => break :hit,
595 .miss => |m| if (!try self.upgradeToExclusiveLock()) {
596 break :digests m.file_digests_populated;
1369 if (disk_file.flags.metadata_only) {
1370 if (!input_path.have_stat) {
1371 // Since this is an input file, FileNotFound counts as a failure, not a cache miss.
1372 const actual_stat = switch (input_path.getHandle(input_file)) {
1373 .none => parent_dir.statFile(io, file_path, .{}) catch |err| switch (err) {
1374 error.Canceled => |e| return e,
1375 else => |e| return fail(diag, .{ .file_stat = .{
1376 .file_offset = file_off,
1377 .err = e,
1378 } }),
5971379 },
1380 .file => |opened_file| opened_file.stat(io) catch |err| switch (err) {
1381 error.Canceled => |e| return e,
1382 else => |e| return fail(diag, .{ .file_stat = .{
1383 .file_offset = file_off,
1384 .err = e,
1385 } }),
1386 },
1387 .dir => |opened_dir| opened_dir.stat(io) catch |err| switch (err) {
1388 error.Canceled => |e| return e,
1389 else => |e| return fail(diag, .{ .file_stat = .{
1390 .file_offset = file_off,
1391 .err = e,
1392 } }),
1393 },
1394 };
1395 // In the other case (have_stat=true), this check is redundant since we already asserted the flags are
1396 // identical, which contains the prefix.
1397 const actual_is_directory = actual_stat.kind == .directory;
1398 if (actual_is_directory != disk_file.flags.is_directory) {
1399 // Since this is an input file, this is a failure, not a cache miss.
1400 return fail(diag, .{ .file_stat = .{
1401 .file_offset = file_off,
1402 .err = if (actual_is_directory) error.IsDir else error.NotDir,
1403 } });
5981404 }
599 // We've just had a miss with the shared lock, and upgraded to an exclusive lock. Someone
600 // else might have modified the digest, so we need to check again before deciding to miss.
601 // Before trying again, we must reset `self.hash.hasher` and `self.files`.
602 // This is basically just the first half of `unhit`.
603 self.hash.hasher = hasher_init;
604 self.hash.hasher.update(&bin_digest);
605 while (self.files.count() != input_file_count) {
606 var file = self.files.pop().?;
607 file.key.deinit(self.cache.gpa);
608 }
609 switch (try self.hitWithCurrentLock()) {
610 .hit => break :hit,
611 .miss => |m| break :digests m.file_digests_populated,
612 }
613 };
614
615 // This is a guaranteed cache miss. We're almost ready to return `false`, but there's a
616 // little bookkeeping to do first. The first `file_digests_populated` entries in `files`
617 // have their `bin_digest` populated; there may be some left in `input_file_count` which
618 // we'll need to populate ourselves. Other than that, this is basically `unhit`.
619 self.manifest_dirty = true;
620 self.hash.hasher = hasher_init;
621 self.hash.hasher.update(&bin_digest);
622 while (self.files.count() != input_file_count) {
623 var file = self.files.pop().?;
624 file.key.deinit(self.cache.gpa);
625 }
626 for (self.files.keys(), 0..) |*file, idx| {
627 if (idx < file_digests_populated) {
628 // `bin_digest` is already populated by `hitWithCurrentLock`, so we can use it directly.
629 self.hash.hasher.update(&file.bin_digest);
630 } else {
631 self.populateFileHash(file) catch |err| {
632 self.diagnostic = .{ .file_hash = .{
633 .file_index = idx,
634 .err = err,
635 } };
636 return error.CacheCheckFailed;
637 };
638 }
1405 const stat: Stat = .init(actual_stat);
1406 const changed = try disk_file.setStatChanged(m, stat);
1407 disk_file.hashFromMetadata();
1408 if (!changed) return .hit;
6391409 }
640 return false;
1410 return .{ .metadata_changed = file_off };
6411411 }
6421412
643 if (self.want_shared_lock) {
644 self.downgradeToSharedLock() catch |err| {
645 self.diagnostic = .{ .manifest_lock = err };
646 return error.CacheCheckFailed;
647 };
648 }
1413 if (input_path.contents.get(m.all_input_content.items)) |file_or_dir_contents| {
1414 var hasher = hasher_init;
1415 hasher.update(file_or_dir_contents);
1416 hasher.final(&disk_file.digest);
6491417
650 return true;
651 }
1418 if (mem.eql(u8, &disk_file.digest, &input_file.digest)) return .hit;
1419 return .{ .contents_changed = file_off };
1420 }
6521421
653 /// Assumes that `self.hash.hasher` has been updated only with the original digest and that
654 /// `self.files` contains only the original input files.
655 fn hitWithCurrentLock(self: *Manifest) HitError!union(enum) {
656 hit,
657 miss: struct {
658 file_digests_populated: usize,
659 },
660 } {
661 const gpa = self.cache.gpa;
662 const io = self.cache.io;
663 const input_file_count = self.files.entries.len;
664 var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded
665 var manifest_reader = self.manifest_file.?.reader(io, &tiny_buffer); // Reads positionally from zero.
666 const limit: std.Io.Limit = .limited(manifest_file_size_max);
667 const file_contents = manifest_reader.interface.allocRemaining(gpa, limit) catch |err| switch (err) {
668 error.OutOfMemory => |e| return e,
669 error.StreamTooLong => return error.OutOfMemory,
670 error.ReadFailed => {
671 self.diagnostic = .{ .manifest_read = manifest_reader.err.? };
672 return error.CacheCheckFailed;
673 },
674 };
675 defer gpa.free(file_contents);
676
677 var any_file_changed = false;
678 var line_iter = mem.tokenizeScalar(u8, file_contents, '\n');
679 var idx: usize = 0;
680 const header_valid = valid: {
681 const line = line_iter.next() orelse break :valid false;
682 break :valid std.mem.eql(u8, line, manifest_header);
683 };
684 if (!header_valid) {
685 return .{ .miss = .{ .file_digests_populated = 0 } };
686 }
687 while (line_iter.next()) |line| {
688 defer idx += 1;
689
690 var iter = mem.tokenizeScalar(u8, line, ' ');
691 const size = iter.next() orelse return error.InvalidFormat;
692 const inode = iter.next() orelse return error.InvalidFormat;
693 const mtime_nsec_str = iter.next() orelse return error.InvalidFormat;
694 const digest_str = iter.next() orelse return error.InvalidFormat;
695 const prefix_str = iter.next() orelse return error.InvalidFormat;
696 const file_path = iter.rest();
697
698 const stat_size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat;
699 const stat_inode = fmt.parseInt(Io.File.INode, inode, 10) catch return error.InvalidFormat;
700 const stat_mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat;
701 const file_bin_digest = b: {
702 if (digest_str.len != hex_digest_len) return error.InvalidFormat;
703 var bd: BinDigest = undefined;
704 _ = fmt.hexToBytes(&bd, digest_str) catch return error.InvalidFormat;
705 break :b bd;
1422 if (disk_file.flags.is_directory) {
1423 const opened_dir = if (input_path.have_handle)
1424 input_path.handle.dir
1425 else
1426 parent_dir.openDir(io, file_path, .{
1427 .iterate = true,
1428 .access_sub_paths = false,
1429 }) catch |err| switch (err) {
1430 error.Canceled => |e| return e,
1431 else => |e| return fail(diag, .{ .file_open = .{
1432 .file_offset = file_off,
1433 .err = e,
1434 } }),
1435 };
1436 defer if (!input_path.request_handle) opened_dir.close(io) else {
1437 input_path.handle = .{ .dir = opened_dir };
1438 input_path.have_handle = true;
7061439 };
7071440
708 const prefix = fmt.parseInt(u8, prefix_str, 10) catch return error.InvalidFormat;
709 if (prefix >= self.cache.prefixes_len) return error.InvalidFormat;
1441 if (!input_path.have_stat) {
1442 const actual_stat = opened_dir.stat(io) catch |err| switch (err) {
1443 error.Canceled => |e| return e,
1444 else => |e| return fail(diag, .{ .file_stat = .{
1445 .file_offset = file_off,
1446 .err = e,
1447 } }),
1448 };
1449 if (!try disk_file.setStatChanged(m, .init(actual_stat))) return .hit;
1450 }
7101451
711 if (file_path.len == 0) return error.InvalidFormat;
1452 const dir_contents_start = m.all_input_content.items.len;
1453 hashDir(gpa, io, opened_dir, &disk_file.digest, &m.all_input_content) catch |err| switch (err) {
1454 error.Canceled, error.OutOfMemory => |e| return e,
1455 else => |e| return fail(diag, .{ .file_read = .{
1456 .file_offset = file_off,
1457 .err = e,
1458 } }),
1459 };
1460 defer switch (input_path.contents.unwrap()) {
1461 .requested => input_path.contents = .populated(.{
1462 .off = @intCast(dir_contents_start),
1463 .len = @intCast(m.all_input_content.items.len - dir_contents_start),
1464 }),
1465 .unrequested => m.all_input_content.shrinkRetainingCapacity(dir_contents_start),
1466 .populated => unreachable,
1467 };
7121468
713 const cache_hash_file = f: {
714 const prefixed_path: PrefixedPath = .{
715 .prefix = prefix,
716 .sub_path = file_path, // expires with file_contents
1469 if (mem.eql(u8, &disk_file.digest, &input_file.digest)) return .hit;
1470 return .{ .contents_changed = file_off };
1471 } else {
1472 const opened_file = if (input_path.have_handle)
1473 input_path.handle.file
1474 else
1475 parent_dir.openFile(io, file_path, .{ .mode = .read_only }) catch |err| switch (err) {
1476 error.Canceled => |e| return e,
1477 else => |e| return fail(diag, .{ .file_open = .{
1478 .file_offset = file_off,
1479 .err = e,
1480 } }),
7171481 };
718 if (idx < input_file_count) {
719 const file = &self.files.keys()[idx];
720 if (!file.prefixed_path.eql(prefixed_path))
721 return error.InvalidFormat;
722
723 file.stat = .{
724 .size = stat_size,
725 .inode = stat_inode,
726 .mtime = .{ .nanoseconds = stat_mtime },
727 };
728 file.bin_digest = file_bin_digest;
729 break :f file;
730 }
731 const gop = try self.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{});
732 errdefer _ = self.files.pop();
733 if (!gop.found_existing) {
734 gop.key_ptr.* = .{
735 .prefixed_path = .{
736 .prefix = prefix,
737 .sub_path = try gpa.dupe(u8, file_path),
738 },
739 .contents = null,
740 .max_file_size = null,
741 .handle = null,
742 .stat = .{
743 .size = stat_size,
744 .inode = stat_inode,
745 .mtime = .{ .nanoseconds = stat_mtime },
746 },
747 .bin_digest = file_bin_digest,
748 };
749 }
750 break :f gop.key_ptr;
1482 defer if (!input_path.request_handle) opened_file.close(io) else {
1483 input_path.handle = .{ .file = opened_file };
1484 input_path.have_handle = true;
7511485 };
7521486
753 const pp = cache_hash_file.prefixed_path;
754 const dir = self.cache.prefixes()[pp.prefix].handle;
755 const this_file = dir.openFile(io, pp.sub_path, .{ .mode = .read_only }) catch |err| switch (err) {
756 error.FileNotFound => {
757 // Every digest before this one has been populated successfully.
758 return .{ .miss = .{ .file_digests_populated = idx } };
1487 if (!input_path.have_stat) {
1488 const actual_stat = opened_file.stat(io) catch |err| switch (err) {
1489 error.Canceled => |e| return e,
1490 else => |e| return fail(diag, .{ .file_stat = .{
1491 .file_offset = file_off,
1492 .err = e,
1493 } }),
1494 };
1495 if (!try disk_file.setStatChanged(m, .init(actual_stat))) return .hit;
1496 }
1497
1498 switch (input_path.contents.unwrap()) {
1499 .requested => {
1500 const start = m.all_input_content.items.len;
1501 hashFileAppend(io, opened_file, &disk_file.digest, &m.all_input_content, gpa) catch |err| switch (err) {
1502 error.Canceled, error.OutOfMemory => |e| return e,
1503 else => |e| return fail(diag, .{ .file_read = .{
1504 .file_offset = file_off,
1505 .err = e,
1506 } }),
1507 };
1508 input_path.contents = .populated(.{
1509 .off = @intCast(start),
1510 .len = @intCast(m.all_input_content.items.len - start),
1511 });
7591512 },
760 error.Canceled => |e| return e,
761 else => |e| {
762 self.diagnostic = .{ .file_open = .{
763 .file_index = idx,
1513 .unrequested => hashFile(io, opened_file, &disk_file.digest) catch |err| switch (err) {
1514 error.Canceled => |e| return e,
1515 else => |e| return fail(diag, .{ .file_read = .{
1516 .file_offset = file_off,
7641517 .err = e,
765 } };
766 return error.CacheCheckFailed;
1518 } }),
7671519 },
1520 .populated => unreachable,
1521 }
1522
1523 if (mem.eql(u8, &disk_file.digest, &input_file.digest)) return .hit;
1524 return .{ .contents_changed = file_off };
1525 }
1526 }
1527
1528 fn checkDiscoveredPath(
1529 m: *Manifest,
1530 diag: *CheckDiagnostic,
1531 file_off: File.Offset,
1532 contents: []u8,
1533 ) CheckError!CheckResult {
1534 const file = file_off.get(contents);
1535 const cache = m.cache;
1536 const gpa = cache.gpa;
1537 const io = cache.io;
1538 const parent_dir = cache.prefixes()[file.flags.prefix].handle;
1539 const file_path = file_off.path(contents);
1540
1541 if (file.flags.metadata_only) {
1542 const actual_stat = parent_dir.statFile(io, file_path, .{}) catch |err| switch (err) {
1543 error.FileNotFound => return .{ .path_deleted = file_off },
1544 error.Canceled => |e| return e,
1545 else => |e| return fail(diag, .{ .file_stat = .{
1546 .file_offset = file_off,
1547 .err = e,
1548 } }),
7681549 };
769 defer this_file.close(io);
770
771 const actual_stat = this_file.stat(io) catch |err| {
772 self.diagnostic = .{ .file_stat = .{
773 .file_index = idx,
774 .err = err,
775 } };
776 return error.CacheCheckFailed;
777 };
778 const size_match = actual_stat.size == cache_hash_file.stat.size;
779 const mtime_match = actual_stat.mtime.nanoseconds == cache_hash_file.stat.mtime.nanoseconds;
780 const inode_match = actual_stat.inode == cache_hash_file.stat.inode;
781
782 if (!size_match or !mtime_match or !inode_match) {
783 cache_hash_file.stat = .{
784 .size = actual_stat.size,
785 .mtime = actual_stat.mtime,
786 .inode = actual_stat.inode,
787 };
7881550
789 if (try self.isProblematicTimestamp(cache_hash_file.stat.mtime)) {
790 // The actual file has an unreliable timestamp, force it to be hashed
791 cache_hash_file.stat.mtime = .zero;
792 cache_hash_file.stat.inode = 0;
793 }
1551 const actual_is_directory = actual_stat.kind == .directory;
1552 if (actual_is_directory != file.flags.is_directory) return .{ .directory_status_changed = file_off };
7941553
795 var actual_digest: BinDigest = undefined;
796 hashFile(io, this_file, &actual_digest) catch |err| {
797 self.diagnostic = .{ .file_read = .{
798 .file_index = idx,
799 .err = err,
800 } };
801 return error.CacheCheckFailed;
802 };
1554 const changed = try file.setStatChanged(m, .init(actual_stat));
1555 file.hashFromMetadata();
1556 if (changed) return .{ .metadata_changed = file_off };
1557 return .hit;
1558 }
8031559
804 if (!mem.eql(u8, &cache_hash_file.bin_digest, &actual_digest)) {
805 cache_hash_file.bin_digest = actual_digest;
806 // keep going until we have the input file digests
807 any_file_changed = true;
808 }
809 }
1560 if (file.flags.is_directory) {
1561 const opened_dir = parent_dir.openDir(io, file_path, .{
1562 .iterate = true,
1563 .access_sub_paths = false,
1564 }) catch |err| switch (err) {
1565 error.FileNotFound => return .{ .path_deleted = file_off },
1566 error.NotDir => return .{ .directory_status_changed = file_off },
1567 error.Canceled => |e| return e,
1568 else => |e| return fail(diag, .{ .file_open = .{
1569 .file_offset = file_off,
1570 .err = e,
1571 } }),
1572 };
1573 defer opened_dir.close(io);
1574
1575 const actual_stat = opened_dir.stat(io) catch |err| switch (err) {
1576 error.Canceled => |e| return e,
1577 else => |e| return fail(diag, .{ .file_stat = .{
1578 .file_offset = file_off,
1579 .err = e,
1580 } }),
1581 };
1582 if (try file.setStatChanged(m, .init(actual_stat))) {
1583 const prev_digest: BinDigest = file.digest;
1584 const dir_contents_start = m.all_input_content.items.len;
1585 defer m.all_input_content.shrinkRetainingCapacity(dir_contents_start);
1586 hashDir(gpa, io, opened_dir, &file.digest, &m.all_input_content) catch |err| switch (err) {
1587 error.Canceled, error.OutOfMemory => |e| return e,
1588 else => |e| return fail(diag, .{ .file_read = .{
1589 .file_offset = file_off,
1590 .err = e,
1591 } }),
1592 };
8101593
811 if (!any_file_changed) {
812 self.hash.hasher.update(&cache_hash_file.bin_digest);
1594 if (!mem.eql(u8, &file.digest, &prev_digest)) return .{ .contents_changed = file_off };
8131595 }
1596 return .hit;
8141597 }
8151598
816 // If the manifest was somehow missing one of our input files, or if any file hash has changed,
817 // then this is a cache miss. However, we have successfully populated some or all of the file
818 // digests.
819 if (any_file_changed or idx < input_file_count) {
820 return .{ .miss = .{ .file_digests_populated = idx } };
1599 const opened_file = parent_dir.openFile(io, file_path, .{ .mode = .read_only }) catch |err| switch (err) {
1600 error.FileNotFound => return .{ .path_deleted = file_off },
1601 error.IsDir => return .{ .directory_status_changed = file_off },
1602 error.Canceled => |e| return e,
1603 else => |e| return fail(diag, .{ .file_open = .{
1604 .file_offset = file_off,
1605 .err = e,
1606 } }),
1607 };
1608 defer opened_file.close(io);
1609
1610 const actual_stat = opened_file.stat(io) catch |err| switch (err) {
1611 error.Canceled => |e| return e,
1612 else => |e| return fail(diag, .{ .file_stat = .{
1613 .file_offset = file_off,
1614 .err = e,
1615 } }),
1616 };
1617
1618 if (try file.setStatChanged(m, .init(actual_stat))) {
1619 const prev_digest: BinDigest = file.digest;
1620 hashFile(io, opened_file, &file.digest) catch |err| switch (err) {
1621 error.Canceled => |e| return e,
1622 else => |e| return fail(diag, .{ .file_read = .{
1623 .file_offset = file_off,
1624 .err = e,
1625 } }),
1626 };
1627
1628 if (!mem.eql(u8, &file.digest, &prev_digest)) return .{ .contents_changed = file_off };
8211629 }
8221630
8231631 return .hit;
8241632 }
8251633
826 /// Reset `self.hash.hasher` to the state it should be in after `hit` returns `false`.
827 /// The hasher contains the original input digest, and all original input file digests (i.e.
828 /// not including post files).
829 /// Assumes that `bin_digest` is populated for all files up to `input_file_count`. As such,
830 /// this is not necessarily safe to call within `hit`.
831 pub fn unhit(self: *Manifest, bin_digest: BinDigest, input_file_count: usize) void {
832 // Reset the hash.
833 self.hash.hasher = hasher_init;
834 self.hash.hasher.update(&bin_digest);
835
836 // Remove files not in the initial hash.
837 while (self.files.count() != input_file_count) {
838 var file = self.files.pop().?;
839 file.key.deinit(self.cache.gpa);
840 }
841
842 for (self.files.keys()) |file| {
843 self.hash.hasher.update(&file.bin_digest);
844 }
1634 fn fail(ptr: *CheckDiagnostic, d: CheckDiagnostic) error{CacheCheckFailed} {
1635 ptr.* = d;
1636 return error.CacheCheckFailed;
8451637 }
8461638
8471639 fn isProblematicTimestamp(man: *Manifest, timestamp: Io.Timestamp) error{Canceled}!bool {
......@@ -887,303 +1679,354 @@ pub const Manifest = struct {
8871679 return timestamp.nanoseconds >= man.recent_problematic_timestamp.nanoseconds;
8881680 }
8891681
890 fn populateFileHash(self: *Manifest, ch_file: *File) !void {
891 const io = self.cache.io;
892
893 if (ch_file.handle) |handle| {
894 return populateFileHashHandle(self, ch_file, handle);
895 } else {
896 const pp = ch_file.prefixed_path;
897 const dir = self.cache.prefixes()[pp.prefix].handle;
898 const handle = try dir.openFile(io, pp.sub_path, .{});
899 defer handle.close(io);
900 return populateFileHashHandle(self, ch_file, handle);
901 }
902 }
903
904 fn populateFileHashHandle(self: *Manifest, ch_file: *File, io_file: Io.File) !void {
905 const io = self.cache.io;
906 const gpa = self.cache.gpa;
1682 pub const AddDiscoveredPathOptions = struct {
1683 discovered_path: DiscoveredPath,
1684 handle: PathHandle = .{ .file = null },
1685 stat: ?Stat = null,
1686 /// If it is a directory, there is a special encoding required for contents, which
1687 /// is null-separated sorted entries, each one prefixed with `File.Kind`.
1688 contents: ?[]const u8 = null,
1689 metadata_only: bool = false,
1690 /// Populated if and only if `error.FileSystemFailure` is returned from `addDiscoveredPath`.
1691 diagnostic: ?*AddDiscoveredPathDiagnostic = null,
1692 };
9071693
908 const actual_stat = try io_file.stat(io);
909 ch_file.stat = .{
910 .size = actual_stat.size,
911 .mtime = actual_stat.mtime,
912 .inode = actual_stat.inode,
913 };
1694 pub const DiscoveredPath = union(enum) {
1695 unresolved: Path,
1696 prefixed: PrefixedPath,
1697 };
9141698
915 if (try self.isProblematicTimestamp(ch_file.stat.mtime)) {
916 // The actual file has an unreliable timestamp, force it to be hashed
917 ch_file.stat.mtime = .zero;
918 ch_file.stat.inode = 0;
1699 pub const AddDiscoveredPathDiagnostic = union(enum) {
1700 none,
1701 open_file: Io.File.OpenError,
1702 open_dir: Io.Dir.OpenError,
1703 stat_file: Io.File.StatError,
1704 stat_dir: Io.Dir.StatError,
1705 read_file: Io.File.ReadPositionalError,
1706 read_dir: Io.Dir.Reader.Error,
1707
1708 pub fn format(this: @This(), w: *Io.Writer) Io.Writer.Error!void {
1709 switch (this) {
1710 .none => return w.writeAll("none"),
1711 else => |err, tag| return w.print("{t}: {t}", .{ tag, err }),
1712 }
9191713 }
1714 };
9201715
921 if (ch_file.max_file_size) |max_file_size| {
922 if (ch_file.stat.size > max_file_size) return error.FileTooBig;
1716 pub const AddDiscoveredPathError = error{
1717 /// If this is returned, diagnostic will be populated.
1718 /// If contents and stat are both provided, this is unreachable.
1719 FileSystemFailure,
1720 } || Allocator.Error || Io.Cancelable;
9231721
924 // Hash while reading from disk, to keep the contents in the cpu
925 // cache while doing hashing.
926 const contents = try gpa.alloc(u8, @intCast(ch_file.stat.size));
927 errdefer gpa.free(contents);
1722 /// Add a file or directory as a dependency of process being cached, after cache miss occurs.
1723 ///
1724 /// See also:
1725 /// * `addInputPath`
1726 /// * `addDiscoveredDepFile`
1727 /// * `addDiscoveredManifest`
1728 pub fn addDiscoveredPath(m: *Manifest, options: AddDiscoveredPathOptions) AddDiscoveredPathError!void {
1729 assert(m.manifest_file != null);
1730 transitionToMissDiscovered(m);
1731 const cache = m.cache;
1732 const gpa = cache.gpa;
1733 const io = cache.io;
1734 const is_directory = options.handle == .dir;
1735
1736 try m.files.ensureUnusedCapacityContext(gpa, 1, .{ .contents = m.contents.items });
1737
1738 const new_file_offset: File.Offset = @fromBackingInt(@intCast(m.contents.items.len));
1739 try m.contents.appendNTimes(gpa, 0, @offsetOf(File, "path_start"));
1740 errdefer m.contents.shrinkRetainingCapacity(@backingInt(new_file_offset));
1741
1742 const new_prefix = switch (options.discovered_path) {
1743 .unresolved => |unresolved| try cache.resolveAppendPath(&m.contents, unresolved),
1744 .prefixed => |prefixed| try cache.appendPrefixedPath(&m.contents, prefixed),
1745 };
1746 assert(mem.isAligned(m.contents.items.len, @alignOf(File)));
1747 new_file_offset.get(m.contents.items).flags = .{
1748 .prefix = new_prefix,
1749 .is_directory = is_directory,
1750 .metadata_only = options.metadata_only,
1751 };
9281752
929 var hasher = hasher_init;
930 var off: usize = 0;
931 while (true) {
932 const bytes_read = try io_file.readPositional(io, &.{contents[off..]}, off);
933 if (bytes_read == 0) break;
934 hasher.update(contents[off..][0..bytes_read]);
935 off += bytes_read;
1753 const gop = m.files.getOrPutAssumeCapacityContext(new_file_offset, .{ .contents = m.contents.items });
1754 m.files.lockPointers();
1755 defer m.files.unlockPointers();
1756
1757 const file_offset = if (gop.found_existing) h: {
1758 m.contents.shrinkRetainingCapacity(@backingInt(new_file_offset));
1759 const existing_off = gop.key_ptr.*;
1760 const header = existing_off.get(m.contents.items);
1761 // If it trips, the same file path has been added to the cache
1762 // manifest both as a directory and as a normal file, making the
1763 // intended caching behavior ambiguous.
1764 assert(header.flags.is_directory == is_directory);
1765 if (!options.metadata_only)
1766 header.flags.metadata_only = false;
1767 break :h existing_off;
1768 } else new_file_offset;
1769
1770 const header = file_offset.get(m.contents.items);
1771
1772 if (options.stat) |stat| {
1773 try header.setStat(m, stat);
1774 if (header.flags.metadata_only) {
1775 header.hashFromMetadata();
1776 } else if (options.contents) |contents| {
1777 var hasher = hasher_init;
1778 hasher.update(contents);
1779 hasher.final(&header.digest);
9361780 }
937 hasher.final(&ch_file.bin_digest);
938
939 ch_file.contents = contents;
940 } else {
941 try hashFile(io, io_file, &ch_file.bin_digest);
1781 return;
9421782 }
9431783
944 self.hash.hasher.update(&ch_file.bin_digest);
945 }
946
947 /// Add a file as a dependency of process being cached, after the initial hash has been
948 /// calculated. This is useful for processes that don't know all the files that
949 /// are depended on ahead of time. For example, a source file that can import other files
950 /// will need to be recompiled if the imported file is changed.
951 pub fn addFilePostFetch(self: *Manifest, file_path: []const u8, max_file_size: usize) ![]const u8 {
952 assert(self.manifest_file != null);
953
954 const gpa = self.cache.gpa;
955 const prefixed_path = try self.cache.findPrefix(file_path);
956 errdefer gpa.free(prefixed_path.sub_path);
957
958 const gop = try self.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{});
959 errdefer _ = self.files.pop();
1784 const need_stat = options.stat == null;
1785 const metadata_only = header.flags.metadata_only;
1786 const prefix = header.flags.prefix;
1787
1788 switch (options.handle) {
1789 .dir => |opt_handle| if (opt_handle) |handle| {
1790 try populateDirectory(m, header, need_stat, handle, options.contents, metadata_only, options.diagnostic);
1791 } else {
1792 const dir = cache.prefixes()[prefix].handle;
1793 const sub_path = file_offset.path(m.contents.items);
1794 const handle = dir.openDir(io, sub_path, .{
1795 .access_sub_paths = false,
1796 .iterate = true,
1797 }) catch |err| switch (err) {
1798 error.Canceled => |e| return e,
1799 else => |e| {
1800 if (options.diagnostic) |d| d.* = .{ .open_dir = e };
1801 return error.FileSystemFailure;
1802 },
1803 };
1804 defer handle.close(io);
1805 try populateDirectory(m, header, need_stat, handle, options.contents, metadata_only, options.diagnostic);
1806 },
9601807
961 if (gop.found_existing) {
962 gpa.free(prefixed_path.sub_path);
963 return gop.key_ptr.contents.?;
1808 .file => |opt_handle| if (opt_handle) |handle| {
1809 try populateFile(m, header, need_stat, handle, options.contents, metadata_only, options.diagnostic);
1810 } else {
1811 const dir = cache.prefixes()[prefix].handle;
1812 const sub_path = file_offset.path(m.contents.items);
1813 const handle = dir.openFile(io, sub_path, .{ .mode = .read_only }) catch |err| switch (err) {
1814 error.Canceled => |e| return e,
1815 else => |e| {
1816 if (options.diagnostic) |d| d.* = .{ .open_file = e };
1817 return error.FileSystemFailure;
1818 },
1819 };
1820 defer handle.close(io);
1821 try populateFile(m, header, need_stat, handle, options.contents, metadata_only, options.diagnostic);
1822 },
9641823 }
965
966 gop.key_ptr.* = .{
967 .prefixed_path = prefixed_path,
968 .max_file_size = max_file_size,
969 .stat = undefined,
970 .bin_digest = undefined,
971 .contents = null,
972 .handle = null,
973 };
974
975 self.files.lockPointers();
976 defer self.files.unlockPointers();
977
978 try self.populateFileHash(gop.key_ptr);
979 return gop.key_ptr.contents.?;
9801824 }
9811825
982 /// Add a file as a dependency of process being cached, after the initial hash has been
983 /// calculated.
984 ///
985 /// This is useful for processes that don't know the all the files that are
986 /// depended on ahead of time. For example, a source file that can import
987 /// other files will need to be recompiled if the imported file is changed.
988 pub fn addFilePost(man: *Manifest, file_path: []const u8) !void {
989 assert(man.manifest_file != null);
990 const gpa = man.cache.gpa;
991 const prefixed_path = try man.cache.findPrefix(file_path);
992 var keep = false;
993 defer if (!keep) gpa.free(prefixed_path.sub_path);
994 keep = try addPrefixedPathPost(man, prefixed_path);
995 }
996
997 pub fn addPathPost(man: *Manifest, path: Path) !void {
998 assert(man.manifest_file != null);
999 const gpa = man.cache.gpa;
1000 const prefixed_path: PrefixedPath = try man.cache.findPrefixPath(path);
1001 var keep = false;
1002 defer if (!keep) gpa.free(prefixed_path.sub_path);
1003 keep = try addPrefixedPathPost(man, prefixed_path);
1004 }
1005
1006 /// Low level function. `prefixed_path` references cloned memory. Returns
1007 /// whether or not `prefixed_path.sub_path` should be kept.
1008 pub fn addPrefixedPathPost(man: *Manifest, prefixed_path: PrefixedPath) !bool {
1009 assert(man.manifest_file != null);
1010 const gpa = man.cache.gpa;
1826 pub const AddDiscoveredDepFileError = error{
1827 InvalidDepFile,
1828 } || AddDiscoveredPathError || Io.File.OpenError || Io.File.Reader.Error;
10111829
1012 const gop = try man.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{});
1013 errdefer _ = man.files.pop();
1014
1015 if (gop.found_existing) return false;
1830 pub const AddDiscoveredDepFileDiagnostic = union(enum) {
1831 add_discovered_path: AddDiscoveredPathDiagnostic,
1832 dep_tokenizer: DepTokenizer.Token,
1833 };
10161834
1017 gop.key_ptr.* = .{
1018 .prefixed_path = prefixed_path,
1019 .max_file_size = null,
1020 .handle = null,
1021 .stat = undefined,
1022 .bin_digest = undefined,
1023 .contents = null,
1835 /// Add a GNU make style dep file as a dependency of process being cached, after cache miss occurs.
1836 ///
1837 /// See also:
1838 /// * `addDiscoveredPath
1839 /// * `addDiscoveredManifest`
1840 pub fn addDiscoveredDepFile(
1841 m: *Manifest,
1842 path: Path,
1843 diagnostic: ?*AddDiscoveredDepFileDiagnostic,
1844 ) AddDiscoveredDepFileError!void {
1845 assert(m.manifest_file != null);
1846 transitionToMissDiscovered(m);
1847
1848 const cache = m.cache;
1849 const gpa = cache.gpa;
1850 const io = cache.io;
1851
1852 // TODO: change DepTokenizer to be streaming rather than operating on slice of bytes.
1853 const prev_len = m.all_input_content.items.len;
1854 defer m.all_input_content.items.len = prev_len;
1855
1856 const file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
1857 defer file.close(io);
1858
1859 var file_reader: Io.File.Reader = .init(file, io, &.{});
1860 file_reader.interface.appendRemainingUnlimited(gpa, &m.all_input_content) catch |err| switch (err) {
1861 error.OutOfMemory => |e| return e,
1862 error.ReadFailed => return file_reader.err.?,
10241863 };
10251864
1026 man.files.lockPointers();
1027 defer man.files.unlockPointers();
1028
1029 try man.populateFileHash(gop.key_ptr);
1030 return true;
1865 var aux: std.ArrayList(u8) = .empty;
1866 defer aux.deinit(gpa);
1867
1868 var it: DepTokenizer = .{ .bytes = m.all_input_content.items[prev_len..] };
1869 while (it.next()) |token| switch (token) {
1870 .target, .target_must_resolve => {},
1871 .prereq => |p| try m.addDiscoveredPath(.{ .discovered_path = .{ .unresolved = .initCwd(p) } }),
1872 .prereq_must_resolve => {
1873 aux.clearRetainingCapacity();
1874 try token.resolve(gpa, &aux);
1875 try m.addDiscoveredPath(.{ .discovered_path = .{ .unresolved = .initCwd(aux.items) } });
1876 },
1877 else => |err| {
1878 if (diagnostic) |d| d.* = .{ .dep_tokenizer = err };
1879 return error.InvalidDepFile;
1880 },
1881 };
10311882 }
10321883
1033 /// Like `addFilePost` but when the file contents have already been loaded from disk.
1034 pub fn addFilePostContents(
1035 man: *Manifest,
1036 file_path: []const u8,
1037 bytes: []const u8,
1038 stat: File.Stat,
1039 ) !void {
1040 assert(man.manifest_file != null);
1041 const gpa = man.cache.gpa;
1042 const prefixed_path = try man.cache.findPrefix(file_path);
1043 var keep = false;
1044 defer if (!keep) gpa.free(prefixed_path.sub_path);
1045 keep = try addPrefixedPathPostContents(man, prefixed_path, bytes, stat);
1884 fn populateFile(
1885 m: *Manifest,
1886 file: *File,
1887 need_stat: bool,
1888 handle: Io.File,
1889 contents: ?[]const u8,
1890 metadata_only: bool,
1891 diagnostic: ?*AddDiscoveredPathDiagnostic,
1892 ) AddDiscoveredPathError!void {
1893 const io = m.cache.io;
1894
1895 if (need_stat) {
1896 const stat = handle.stat(io) catch |err| switch (err) {
1897 error.Canceled => |e| return e,
1898 else => |e| {
1899 if (diagnostic) |d| d.* = .{ .stat_file = e };
1900 return error.FileSystemFailure;
1901 },
1902 };
1903 try file.setStat(m, .init(stat));
1904 }
1905 if (metadata_only) {
1906 file.hashFromMetadata();
1907 return;
1908 }
1909 if (contents) |bytes| {
1910 var hasher = hasher_init;
1911 hasher.update(bytes);
1912 hasher.final(&file.digest);
1913 } else {
1914 hashFile(io, handle, &file.digest) catch |err| switch (err) {
1915 error.Canceled => |e| return e,
1916 else => |e| {
1917 if (diagnostic) |d| d.* = .{ .read_file = e };
1918 return error.FileSystemFailure;
1919 },
1920 };
1921 }
10461922 }
10471923
1048 /// Low level function. `prefixed_path` references cloned memory. Returns
1049 /// whether or not `prefixed_path.sub_path` should be kept.
1050 pub fn addPrefixedPathPostContents(
1051 man: *Manifest,
1052 prefixed_path: PrefixedPath,
1053 bytes: []const u8,
1054 stat: File.Stat,
1055 ) !bool {
1056 const gpa = man.cache.gpa;
1057 const gop = try man.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{});
1058 errdefer _ = man.files.pop();
1059
1060 if (gop.found_existing) return false;
1061
1062 const new_file = gop.key_ptr;
1063
1064 new_file.* = .{
1065 .prefixed_path = prefixed_path,
1066 .max_file_size = null,
1067 .handle = null,
1068 .stat = stat,
1069 .bin_digest = undefined,
1070 .contents = null,
1071 };
1072
1073 if (try man.isProblematicTimestamp(new_file.stat.mtime)) {
1074 // The actual file has an unreliable timestamp, force it to be hashed
1075 new_file.stat.mtime = .zero;
1076 new_file.stat.inode = 0;
1924 fn populateDirectory(
1925 m: *Manifest,
1926 file: *File,
1927 need_stat: bool,
1928 handle: Io.Dir,
1929 contents: ?[]const u8,
1930 metadata_only: bool,
1931 diagnostic: ?*AddDiscoveredPathDiagnostic,
1932 ) AddDiscoveredPathError!void {
1933 const cache = m.cache;
1934 const io = cache.io;
1935 const gpa = cache.gpa;
1936
1937 if (need_stat) {
1938 const stat = handle.stat(io) catch |err| switch (err) {
1939 error.Canceled => |e| return e,
1940 else => |e| {
1941 if (diagnostic) |d| d.* = .{ .stat_dir = e };
1942 return error.FileSystemFailure;
1943 },
1944 };
1945 try file.setStat(m, .init(stat));
10771946 }
1078
1079 {
1947 if (metadata_only) {
1948 file.hashFromMetadata();
1949 return;
1950 }
1951 if (contents) |bytes| {
10801952 var hasher = hasher_init;
10811953 hasher.update(bytes);
1082 hasher.final(&new_file.bin_digest);
1954 hasher.final(&file.digest);
1955 } else {
1956 const prev_contents_len = m.all_input_content.items.len;
1957 defer m.all_input_content.shrinkRetainingCapacity(prev_contents_len);
1958 hashDir(gpa, io, handle, &file.digest, &m.all_input_content) catch |err| switch (err) {
1959 error.OutOfMemory, error.Canceled => |e| return e,
1960 else => |e| {
1961 if (diagnostic) |d| d.* = .{ .read_dir = e };
1962 return error.FileSystemFailure;
1963 },
1964 };
10831965 }
1966 }
10841967
1085 man.hash.hasher.update(&new_file.bin_digest);
1086 return true;
1968 /// See also `hitDigestHex`.
1969 pub fn hitDigest(m: *Manifest) BinDigest {
1970 assert(m.manifest_file != null);
1971 assert(m.state == .hit);
1972 m.state = .hit_digested;
1973 return finalDigest(m);
10871974 }
10881975
1089 pub fn addDepFilePost(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void {
1090 assert(self.manifest_file != null);
1091 return self.addDepFileMaybePost(dir, dep_file_sub_path);
1976 pub fn hitDigestHex(m: *Manifest) HexDigest {
1977 return binToHex(m.hitDigest());
10921978 }
10931979
1094 fn addDepFileMaybePost(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void {
1095 const gpa = self.cache.gpa;
1096 const io = self.cache.io;
1097 const dep_file_contents = try dir.readFileAlloc(io, dep_file_sub_path, gpa, .limited(manifest_file_size_max));
1098 defer gpa.free(dep_file_contents);
1099
1100 var error_buf: std.ArrayList(u8) = .empty;
1101 defer error_buf.deinit(gpa);
1102
1103 var resolve_buf: std.ArrayList(u8) = .empty;
1104 defer resolve_buf.deinit(gpa);
1105
1106 var it: DepTokenizer = .{ .bytes = dep_file_contents };
1107 while (it.next()) |token| {
1108 switch (token) {
1109 // We don't care about targets, we only want the prereqs
1110 // Clang is invoked in single-source mode but other programs may not
1111 .target, .target_must_resolve => {},
1112 .prereq => |file_path| if (self.manifest_file == null) {
1113 _ = try self.addFilePath(.initCwd(file_path), null);
1114 } else try self.addFilePost(file_path),
1115 .prereq_must_resolve => {
1116 resolve_buf.clearRetainingCapacity();
1117 try token.resolve(gpa, &resolve_buf);
1118 if (self.manifest_file == null) {
1119 _ = try self.addFilePath(.initCwd(resolve_buf.items), null);
1120 } else try self.addFilePost(resolve_buf.items);
1121 },
1122 else => |err| {
1123 try err.printError(gpa, &error_buf);
1124 log.err("failed parsing {s}: {s}", .{ dep_file_sub_path, error_buf.items });
1125 return error.InvalidDepFile;
1126 },
1127 }
1128 }
1980 /// See also `missDigestHex`.
1981 pub fn missDigest(m: *Manifest) BinDigest {
1982 assert(m.manifest_file != null);
1983 transitionToMissDiscovered(m);
1984 m.state = .miss_digested;
1985 return finalDigest(m);
11291986 }
11301987
1131 /// Returns a binary hash of the inputs.
1132 pub fn finalBin(self: *Manifest) BinDigest {
1133 assert(self.manifest_file != null);
1988 pub fn missDigestHex(m: *Manifest) HexDigest {
1989 return binToHex(m.missDigest());
1990 }
11341991
1135 // We don't close the manifest file yet, because we want to
1136 // keep it locked until the API user is done using it.
1137 // We also don't write out the manifest yet, because until
1138 // cache_release is called we still might be working on creating
1139 // the artifacts to cache.
1992 fn finalDigest(m: *Manifest) BinDigest {
1993 const contents = m.contents.items;
1994 const hasher = &m.hash.hasher;
11401995
1996 for (m.files.keys()) |off| {
1997 const file = off.get(contents);
1998 hasher.update(&file.digest);
1999 }
2000 // We don't close the manifest file yet, because we want to keep it locked until the API user is done using it.
2001 // We also don't write out the manifest yet, because until `finalize` is called we still might be working on
2002 // creating the artifacts to cache.
11412003 var bin_digest: BinDigest = undefined;
1142 self.hash.hasher.final(&bin_digest);
2004 hasher.final(&bin_digest);
11432005 return bin_digest;
11442006 }
11452007
1146 /// Returns a hex encoded hash of the inputs.
1147 pub fn final(self: *Manifest) HexDigest {
1148 const bin_digest = self.finalBin();
1149 return binToHex(bin_digest);
1150 }
1151
11522008 /// If `want_shared_lock` is true, this function automatically downgrades the
11532009 /// lock from exclusive to shared.
1154 pub fn writeManifest(self: *Manifest) !void {
1155 assert(self.have_exclusive_lock);
1156 const io = self.cache.io;
1157 const manifest_file = self.manifest_file.?;
1158 if (self.manifest_dirty) {
1159 self.manifest_dirty = false;
1160
1161 var buffer: [4000]u8 = undefined;
1162 var fw = manifest_file.writer(io, &buffer);
1163 writeDirtyManifestToStream(self, &fw) catch |err| switch (err) {
1164 error.WriteFailed => return fw.err.?,
1165 else => |e| return e,
1166 };
1167 }
2010 pub fn finalize(m: *Manifest) !void {
2011 const io = m.cache.io;
2012 const manifest_file = m.manifest_file.?;
2013
2014 assert(m.state == .miss_digested);
2015 assert(m.have_exclusive_lock);
11682016
1169 if (self.want_shared_lock) {
1170 try self.downgradeToSharedLock();
2017 {
2018 m.contents.appendAssumeCapacity(0);
2019 defer _ = m.contents.pop().?;
2020
2021 try manifest_file.setLength(io, m.contents.items.len);
2022 try manifest_file.writePositionalAll(io, m.contents.items, 0);
2023
2024 m.state = .miss_finalized;
11712025 }
1172 }
11732026
1174 fn writeDirtyManifestToStream(self: *Manifest, fw: *Io.File.Writer) !void {
1175 try fw.interface.writeAll(manifest_header ++ "\n");
1176 for (self.files.keys()) |file| {
1177 try fw.interface.print("{d} {d} {d} {x} {d} {s}\n", .{
1178 file.stat.size,
1179 file.stat.inode,
1180 file.stat.mtime,
1181 &file.bin_digest,
1182 file.prefixed_path.prefix,
1183 file.prefixed_path.sub_path,
1184 });
2027 if (m.want_shared_lock) {
2028 try m.downgradeToSharedLock();
11852029 }
1186 try fw.end();
11872030 }
11882031
11892032 fn downgradeToSharedLock(self: *Manifest) !void {
......@@ -1198,53 +2041,74 @@ pub const Manifest = struct {
11982041 self.have_exclusive_lock = false;
11992042 }
12002043
1201 fn upgradeToExclusiveLock(self: *Manifest) error{CacheCheckFailed}!bool {
1202 if (self.have_exclusive_lock) return false;
1203 assert(self.manifest_file != null);
1204 const io = self.cache.io;
2044 fn upgradeToExclusiveLock(m: *Manifest, diag: *CheckDiagnostic) error{CacheCheckFailed}!bool {
2045 if (m.have_exclusive_lock) return false;
2046 assert(m.manifest_file != null);
2047 const io = m.cache.io;
12052048
12062049 if (std.process.can_spawn or !builtin.single_threaded) {
1207 const manifest_file = self.manifest_file.?;
2050 const manifest_file = m.manifest_file.?;
12082051 // Here we intentionally have a period where the lock is released, in case there are
12092052 // other processes holding a shared lock.
12102053 manifest_file.unlock(io);
1211 manifest_file.lock(io, .exclusive) catch |err| {
1212 self.diagnostic = .{ .manifest_lock = err };
1213 return error.CacheCheckFailed;
1214 };
2054 manifest_file.lock(io, .exclusive) catch |err| return fail(diag, .{ .manifest_lock = err });
12152055 }
1216 self.have_exclusive_lock = true;
2056 m.have_exclusive_lock = true;
12172057 return true;
12182058 }
12192059
12202060 /// Obtain only the data needed to maintain a lock on the manifest file.
12212061 /// The `Manifest` remains safe to deinit.
12222062 ///
1223 /// Don't forget to call `writeManifest` before this!
2063 /// Don't forget to call `finalize` before this!
12242064 pub fn toOwnedLock(self: *Manifest) Lock {
12252065 defer self.manifest_file = null;
12262066 return .{ .manifest_file = self.manifest_file.? };
12272067 }
12282068
1229 pub fn takeFiles(man: *Manifest) Files {
1230 defer man.files = .empty;
1231 return man.files;
2069 pub const SelfContainedFiles = struct {
2070 /// References memory inside `contents`.
2071 files: Files,
2072 contents: std.ArrayList(u8),
2073
2074 pub const empty: @This() = .{
2075 .files = .empty,
2076 .contents = .empty,
2077 };
2078
2079 pub fn deinit(scf: *SelfContainedFiles, gpa: Allocator) void {
2080 scf.files.deinit(gpa);
2081 scf.contents.deinit(gpa);
2082 scf.* = undefined;
2083 }
2084
2085 pub fn path(scf: *const SelfContainedFiles, file_offset: File.Offset) [:0]const u8 {
2086 return file_offset.path(scf.contents.items);
2087 }
2088 };
2089
2090 pub fn takeFiles(m: *Manifest) SelfContainedFiles {
2091 defer m.files = .empty;
2092 defer m.contents = .empty;
2093 return borrowFiles(m);
12322094 }
12332095
1234 pub fn freeFiles(gpa: Allocator, files: *Files) void {
1235 for (files.keys()) |*file| file.deinit(gpa);
1236 files.deinit(gpa);
2096 pub fn borrowFiles(m: *const Manifest) SelfContainedFiles {
2097 return .{
2098 .files = m.files,
2099 .contents = m.contents,
2100 };
12372101 }
12382102
12392103 /// Releases the manifest file and frees any memory the Manifest was using.
12402104 /// `Manifest.hit` must be called first.
12412105 ///
1242 /// Don't forget to call `writeManifest` before this!
1243 pub fn deinit(man: *Manifest) void {
1244 const io = man.cache.io;
1245 const gpa = man.cache.gpa;
2106 /// Don't forget to call `finalize` before this!
2107 pub fn deinit(m: *Manifest) void {
2108 const io = m.cache.io;
2109 const gpa = m.cache.gpa;
12462110
1247 if (man.manifest_file) |file| {
2111 if (m.manifest_file) |file| {
12482112 if (builtin.os.tag == .windows) {
12492113 // See Lock.release for why this is required on Windows
12502114 file.unlock(io);
......@@ -1252,20 +2116,25 @@ pub const Manifest = struct {
12522116
12532117 file.close(io);
12542118 }
1255 freeFiles(gpa, &man.files);
1256 man.* = undefined;
2119 m.files.deinit(gpa);
2120 m.contents.deinit(gpa);
2121 m.input_paths.deinit(gpa);
2122 m.all_input_content.deinit(gpa);
2123 m.* = undefined;
12572124 }
12582125
1259 pub fn populateFileSystemInputs(man: *Manifest, buf: *std.ArrayList(u8)) Allocator.Error!void {
2126 pub fn populateFileSystemInputs(man: *const Manifest, buf: *std.ArrayList(u8)) Allocator.Error!void {
12602127 assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == man.cache.prefixes_len);
12612128 buf.clearRetainingCapacity();
12622129 const gpa = man.cache.gpa;
12632130 const files = man.files.keys();
12642131 if (files.len > 0) {
2132 const contents = man.contents.items;
12652133 for (files) |file| {
1266 try buf.ensureUnusedCapacity(gpa, file.prefixed_path.sub_path.len + 2);
1267 buf.appendAssumeCapacity(file.prefixed_path.prefix + 1);
1268 buf.appendSliceAssumeCapacity(file.prefixed_path.sub_path);
2134 const file_path = file.path(contents);
2135 try buf.ensureUnusedCapacity(gpa, file_path.len + 2);
2136 buf.appendAssumeCapacity(@as(u8, file.get(contents).flags.prefix) + 1);
2137 buf.appendSliceAssumeCapacity(file_path);
12692138 buf.appendAssumeCapacity(0);
12702139 }
12712140 // The null byte is a separator, not a terminator.
......@@ -1273,53 +2142,157 @@ pub const Manifest = struct {
12732142 }
12742143 }
12752144
1276 pub fn populateOtherManifest(man: *Manifest, other: *Manifest, prefix_map: [5]u8) Allocator.Error!void {
1277 const gpa = other.cache.gpa;
1278 assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == man.cache.prefixes_len);
1279 assert(man.cache.prefixes_len == 5);
1280 for (man.files.keys()) |file| {
1281 const prefixed_path: PrefixedPath = .{
1282 .prefix = prefix_map[file.prefixed_path.prefix],
1283 .sub_path = try gpa.dupe(u8, file.prefixed_path.sub_path),
1284 };
1285 errdefer gpa.free(prefixed_path.sub_path);
2145 /// Add the full set of paths from another `Manifest` as a dependency of process being cached, after cache miss
2146 /// occurs.
2147 ///
2148 /// See also:
2149 /// * `addDiscoveredPath
2150 /// * `addDiscoveredDepFile`
2151 pub fn addDiscoveredManifest(m: *Manifest, discovered: *const Manifest, prefix_map: [5]u8) Allocator.Error!void {
2152 const gpa = m.cache.gpa;
2153 assert(m.manifest_file != null);
2154 transitionToMissDiscovered(m);
2155 assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == discovered.cache.prefixes_len);
2156 assert(discovered.cache.prefixes_len == 5);
2157
2158 const discovered_files = discovered.files.keys();
2159 const orig_files_len = m.files.count();
2160 const orig_contents_len = m.contents.items.len;
2161 errdefer {
2162 m.files.shrinkRetainingCapacityContext(orig_files_len, .{ .contents = m.contents.items });
2163 m.contents.shrinkRetainingCapacity(orig_contents_len);
2164 }
2165
2166 for (discovered_files, 0..) |off, file_index| {
2167 try m.files.ensureUnusedCapacityContext(gpa, 1, .{ .contents = m.contents.items });
2168
2169 const next_index = file_index + 1;
2170 const next_off = if (discovered_files.len - next_index == 0)
2171 discovered.contents.items.len
2172 else
2173 @backingInt(discovered_files[next_index]);
12862174
1287 const gop = try other.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{});
1288 errdefer _ = other.files.pop();
2175 const copy_bytes = discovered.contents.items[@backingInt(off)..next_off];
2176 const prev_contents_len: File.Offset = @fromBackingInt(@intCast(m.contents.items.len));
2177 try m.contents.appendSlice(gpa, copy_bytes);
2178
2179 const gop = m.files.getOrPutAssumeCapacityContext(prev_contents_len, .{
2180 .contents = m.contents.items,
2181 });
12892182
12902183 if (gop.found_existing) {
1291 gpa.free(prefixed_path.sub_path);
2184 m.contents.shrinkRetainingCapacity(@backingInt(prev_contents_len));
12922185 continue;
12932186 }
12942187
1295 gop.key_ptr.* = .{
1296 .prefixed_path = prefixed_path,
1297 .max_file_size = file.max_file_size,
1298 .handle = file.handle,
1299 .stat = file.stat,
1300 .bin_digest = file.bin_digest,
1301 .contents = null,
1302 };
2188 // Flags are already copied but the prefix is supposed to be filtered by `prefix_map`.
2189 const other_file = prev_contents_len.get(m.contents.items);
2190 other_file.flags.prefix = @intCast(prefix_map[other_file.flags.prefix]);
2191 }
2192 }
13032193
1304 other.hash.hasher.update(&gop.key_ptr.bin_digest);
2194 fn hashFile(io: Io, file: Io.File, bin_digest: *[Hasher.mac_length]u8) Io.File.ReadPositionalError!void {
2195 var buffer: [2048]u8 = undefined;
2196 var hasher = hasher_init;
2197 var offset: u64 = 0;
2198 while (true) {
2199 const n = try file.readPositional(io, &.{&buffer}, offset);
2200 if (n == 0) break;
2201 hasher.update(buffer[0..n]);
2202 offset += n;
13052203 }
2204 hasher.final(bin_digest);
13062205 }
1307};
13082206
1309fn hashFile(io: Io, file: Io.File, bin_digest: *[Hasher.mac_length]u8) Io.File.ReadPositionalError!void {
1310 var buffer: [2048]u8 = undefined;
1311 var hasher = hasher_init;
1312 var offset: u64 = 0;
1313 while (true) {
1314 const n = try file.readPositional(io, &.{&buffer}, offset);
1315 if (n == 0) break;
1316 hasher.update(buffer[0..n]);
1317 offset += n;
2207 fn hashFileAppend(
2208 io: Io,
2209 file: Io.File,
2210 bin_digest: *[Hasher.mac_length]u8,
2211 al: *std.ArrayList(u8),
2212 gpa: Allocator,
2213 ) (Io.File.ReadPositionalError || Allocator.Error)!void {
2214 var hasher = hasher_init;
2215 var offset: u64 = 0;
2216 while (true) {
2217 try al.ensureUnusedCapacity(gpa, 2048);
2218 const buffer = al.unusedCapacitySlice();
2219 const n = try file.readPositional(io, &.{buffer}, offset);
2220 if (n == 0) break;
2221 hasher.update(buffer[0..n]);
2222 offset += n;
2223 al.items.len += n;
2224 }
2225 hasher.final(bin_digest);
13182226 }
1319 hasher.final(bin_digest);
1320}
13212227
1322// Create/Write a file, close it, then grab its stat.mtime timestamp.
2228 const HashDirError = Io.Dir.Reader.Error || Allocator.Error;
2229
2230 /// Appends the sorted, encoded directory entries to `contents`.
2231 fn hashDir(
2232 gpa: Allocator,
2233 io: Io,
2234 dir: Io.Dir,
2235 bin_digest: *[Hasher.mac_length]u8,
2236 contents: *std.ArrayList(u8),
2237 ) HashDirError!void {
2238 var buffer: [@max(2048, Io.Dir.Reader.min_buffer_len)]u8 align(@alignOf(usize)) = undefined;
2239 var reader: Io.Dir.Reader = .init(dir, &buffer);
2240 var entry_buffer: [16]Io.Dir.Entry = undefined;
2241
2242 const contents_start = contents.items.len;
2243 errdefer contents.shrinkRetainingCapacity(contents_start);
2244
2245 // Each index points into `contents`.
2246 var entries_list: std.ArrayList(u32) = .empty;
2247 defer entries_list.deinit(gpa);
2248
2249 while (reader.state != .finished) {
2250 const entries = entry_buffer[0..try reader.read(io, &entry_buffer)];
2251 for (try entries_list.addManyAsSlice(gpa, entries.len), entries) |*off, entry| {
2252 off.* = @intCast(contents.items.len);
2253 // As an optimization, make the reservation also count the duplication
2254 // of the contents buffer that will be required after sorting.
2255 try contents.ensureUnusedCapacity(gpa, (contents.items.len + entry.name.len + 2 - contents_start) * 2);
2256 contents.appendAssumeCapacity(@backingInt(Manifest.File.Kind.fromStat(entry.kind)));
2257 contents.appendSliceAssumeCapacity(entry.name);
2258 contents.appendAssumeCapacity(0);
2259 }
2260 }
2261
2262 const Sort = struct {
2263 contents: [*:0]const u8,
2264 pub fn lessThan(this: @This(), lhs: u32, rhs: u32) bool {
2265 // This comparison includes the kind byte.
2266 return mem.lessThanZ(u8, this.contents + lhs, this.contents + rhs);
2267 }
2268 };
2269 mem.sortUnstable(u32, entries_list.items, @as(Sort, .{
2270 .contents = @ptrCast(contents.items.ptr),
2271 }), Sort.lessThan);
2272
2273 // Duplicate the contents such that we may refer to it while creating a
2274 // sorted copy in the original position (at contents_start). We will then
2275 // offset all the entries_list offsets by contents len when reading from the unsorted copy.
2276 const contents_len = contents.items.len - contents_start;
2277 @memcpy(contents.addManyAsSliceAssumeCapacity(contents_len), contents.items[contents_start..][0..contents_len]);
2278
2279 var new_offset: usize = contents_start;
2280 for (entries_list.items) |wrong_offset| {
2281 const offset = wrong_offset + contents_len;
2282 // Includes the kind prefix which we also want to copy.
2283 const entry: [*:0]const u8 = @ptrCast(contents.items[offset..]);
2284 new_offset += mem.copySentinelInclusive(u8, 0, contents.items[new_offset..], entry);
2285 }
2286 assert(new_offset == contents_start + contents_len);
2287 contents.shrinkRetainingCapacity(contents_start + contents_len);
2288
2289 var hasher = hasher_init;
2290 hasher.update(contents.items[contents_start..][0..contents_len]);
2291 hasher.final(bin_digest);
2292 }
2293};
2294
2295/// Create/Write a file, close it, then grab its stat.mtime timestamp.
13232296fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp {
13242297 const test_out_file = "test-filetimestamp.tmp";
13252298
......@@ -1348,11 +2321,16 @@ test "cache file and then recall it" {
13482321 const temp_manifest_dir = "temp_manifest_dir";
13492322
13502323 try tmp.dir.writeFile(io, .{ .sub_path = temp_file, .data = "Hello, world!\n" });
2324 const tmp_directory: Directory = .{
2325 .path = try std.fs.path.join(testing.allocator, &.{std.testing.TmpDir.parent_dir_path}),
2326 .handle = tmp.dir,
2327 };
2328 defer testing.allocator.free(tmp_directory.path.?);
13512329
13522330 // Wait for file timestamps to tick
13532331 const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir);
13542332 while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) {
1355 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
2333 try Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
13562334 }
13572335
13582336 var digest1: HexDigest = undefined;
......@@ -1365,45 +2343,53 @@ test "cache file and then recall it" {
13652343 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
13662344 .cwd = cwd,
13672345 };
1368 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
2346 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
2347 cache.addPrefix(tmp_directory);
13692348 defer cache.manifest_dir.close(io);
13702349
13712350 {
1372 var ch = cache.obtain();
1373 defer ch.deinit();
1374
1375 ch.hash.add(true);
1376 ch.hash.add(@as(u16, 1234));
1377 ch.hash.addBytes("1234");
1378 _ = try ch.addFilePath(.initCwd(temp_file), null);
1379
1380 // There should be nothing in the cache
1381 try testing.expectEqual(false, try ch.hit(.none));
1382
1383 digest1 = ch.final();
1384 try ch.writeManifest();
2351 var man = cache.obtain();
2352 defer man.deinit();
2353
2354 man.hash.add(true);
2355 man.hash.add(@as(u16, 1234));
2356 man.hash.addBytes("1234");
2357 _ = try man.addInputPath(.{
2358 .root_dir = tmp_directory,
2359 .sub_path = temp_file,
2360 }, .{});
2361
2362 var diag: Manifest.CheckDiagnostic = undefined;
2363 try testing.expectEqual(.incomplete_manifest, try man.check(&diag, .none));
2364
2365 digest1 = man.missDigestHex();
2366 try man.finalize();
13852367 }
13862368 {
1387 var ch = cache.obtain();
1388 defer ch.deinit();
2369 var man = cache.obtain();
2370 defer man.deinit();
13892371
1390 ch.hash.add(true);
1391 ch.hash.add(@as(u16, 1234));
1392 ch.hash.addBytes("1234");
1393 _ = try ch.addFilePath(.initCwd(temp_file), null);
2372 man.hash.add(true);
2373 man.hash.add(@as(u16, 1234));
2374 man.hash.addBytes("1234");
2375 _ = try man.addInputPath(.{
2376 .root_dir = tmp_directory,
2377 .sub_path = temp_file,
2378 }, .{});
13942379
13952380 // Cache hit! We just "built" the same file
1396 try testing.expect(try ch.hit(.none));
1397 digest2 = ch.final();
2381 var diag: Manifest.CheckDiagnostic = undefined;
2382 try testing.expectEqual(.hit, try man.check(&diag, .none));
2383 digest2 = man.hitDigestHex();
13982384
1399 try testing.expectEqual(false, ch.have_exclusive_lock);
2385 try testing.expectEqual(false, man.have_exclusive_lock);
14002386 }
14012387
14022388 try testing.expectEqual(digest1, digest2);
14032389 }
14042390}
14052391
1406test "check that changing a file makes cache fail" {
2392test "check that changing a file causes cache miss" {
14072393 const io = testing.io;
14082394
14092395 var tmp = testing.tmpDir(.{});
......@@ -1418,11 +2404,16 @@ test "check that changing a file makes cache fail" {
14182404 const updated_temp_file_contents = "Hello, world; but updated!\n";
14192405
14202406 try tmp.dir.writeFile(io, .{ .sub_path = temp_file, .data = original_temp_file_contents });
2407 const tmp_directory: Directory = .{
2408 .path = try std.fs.path.join(testing.allocator, &.{std.testing.TmpDir.parent_dir_path}),
2409 .handle = tmp.dir,
2410 };
2411 defer testing.allocator.free(tmp_directory.path.?);
14212412
14222413 // Wait for file timestamps to tick
14232414 const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir);
14242415 while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) {
1425 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
2416 try Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
14262417 }
14272418
14282419 var digest1: HexDigest = undefined;
......@@ -1435,47 +2426,57 @@ test "check that changing a file makes cache fail" {
14352426 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
14362427 .cwd = cwd,
14372428 };
1438 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
2429 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
2430 cache.addPrefix(tmp_directory);
14392431 defer cache.manifest_dir.close(io);
14402432
14412433 {
1442 var ch = cache.obtain();
1443 defer ch.deinit();
2434 var man = cache.obtain();
2435 defer man.deinit();
14442436
1445 ch.hash.addBytes("1234");
1446 const temp_file_idx = try ch.addFilePath(.initCwd(temp_file), 100);
2437 man.hash.addBytes("1234");
2438 const temp_file_idx = try man.addInputPath(.{
2439 .root_dir = tmp_directory,
2440 .sub_path = temp_file,
2441 }, .{ .request_contents = true });
14472442
1448 // There should be nothing in the cache
1449 try testing.expectEqual(false, try ch.hit(.none));
2443 var diag: Manifest.CheckDiagnostic = undefined;
2444 try testing.expectEqual(.incomplete_manifest, try man.check(&diag, .none));
14502445
1451 try testing.expect(mem.eql(u8, original_temp_file_contents, ch.files.keys()[temp_file_idx].contents.?));
2446 try testing.expectEqualStrings(original_temp_file_contents, temp_file_idx.contents(&man));
14522447
1453 digest1 = ch.final();
2448 digest1 = man.missDigestHex();
14542449
1455 try ch.writeManifest();
2450 try man.finalize();
14562451 }
14572452
14582453 try tmp.dir.writeFile(io, .{ .sub_path = temp_file, .data = updated_temp_file_contents });
14592454
14602455 {
1461 var ch = cache.obtain();
1462 defer ch.deinit();
2456 var man = cache.obtain();
2457 defer man.deinit();
14632458
1464 ch.hash.addBytes("1234");
1465 const temp_file_idx = try ch.addFilePath(.initCwd(temp_file), 100);
2459 man.hash.addBytes("1234");
2460 const temp_file_idx = try man.addInputPath(.{
2461 .root_dir = tmp_directory,
2462 .sub_path = temp_file,
2463 }, .{ .request_contents = true });
14662464
1467 // A file that we depend on has been updated, so the cache should not contain an entry for it
1468 try testing.expectEqual(false, try ch.hit(.none));
2465 // The one input file changed.
2466 var diag: Manifest.CheckDiagnostic = undefined;
2467 try testing.expectEqual(
2468 @as(Manifest.CheckResult, .{ .contents_changed = temp_file_idx.offset(&man) }),
2469 try man.check(&diag, .none),
2470 );
14692471
1470 // The cache system does not keep the contents of re-hashed input files.
1471 try testing.expect(ch.files.keys()[temp_file_idx].contents == null);
2472 try testing.expectEqualStrings(updated_temp_file_contents, temp_file_idx.contents(&man));
14722473
1473 digest2 = ch.final();
2474 digest2 = man.missDigestHex();
14742475
1475 try ch.writeManifest();
2476 try man.finalize();
14762477 }
14772478
1478 try testing.expect(!mem.eql(u8, digest1[0..], digest2[0..]));
2479 try testing.expect(!mem.eql(u8, &digest1, &digest2));
14792480 }
14802481}
14812482
......@@ -1508,12 +2509,12 @@ test "no file inputs" {
15082509
15092510 man.hash.addBytes("1234");
15102511
1511 // There should be nothing in the cache
1512 try testing.expectEqual(false, try man.hit(.none));
2512 var diag: Manifest.CheckDiagnostic = undefined;
2513 try testing.expectEqual(.incomplete_manifest, try man.check(&diag, .none));
15132514
1514 digest1 = man.final();
2515 digest1 = man.missDigestHex();
15152516
1516 try man.writeManifest();
2517 try man.finalize();
15172518 }
15182519 {
15192520 var man = cache.obtain();
......@@ -1521,15 +2522,16 @@ test "no file inputs" {
15212522
15222523 man.hash.addBytes("1234");
15232524
1524 try testing.expect(try man.hit(.none));
1525 digest2 = man.final();
2525 var diag: Manifest.CheckDiagnostic = undefined;
2526 try testing.expectEqual(.hit, try man.check(&diag, .none));
2527 digest2 = man.hitDigestHex();
15262528 try testing.expectEqual(false, man.have_exclusive_lock);
15272529 }
15282530
15292531 try testing.expectEqual(digest1, digest2);
15302532}
15312533
1532test "Manifest with files added after initial hash work" {
2534test "Manifest with files added after initial hash" {
15332535 const io = testing.io;
15342536
15352537 var tmp = testing.tmpDir(.{});
......@@ -1544,11 +2546,16 @@ test "Manifest with files added after initial hash work" {
15442546
15452547 try tmp.dir.writeFile(io, .{ .sub_path = temp_file1, .data = "Hello, world!\n" });
15462548 try tmp.dir.writeFile(io, .{ .sub_path = temp_file2, .data = "Hello world the second!\n" });
2549 const tmp_directory: Directory = .{
2550 .path = try std.fs.path.join(testing.allocator, &.{std.testing.TmpDir.parent_dir_path}),
2551 .handle = tmp.dir,
2552 };
2553 defer testing.allocator.free(tmp_directory.path.?);
15472554
15482555 // Wait for file timestamps to tick
15492556 const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir);
15502557 while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) {
1551 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
2558 try Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
15522559 }
15532560
15542561 var digest1: HexDigest = undefined;
......@@ -1562,35 +2569,46 @@ test "Manifest with files added after initial hash work" {
15622569 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
15632570 .cwd = cwd,
15642571 };
1565 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
2572 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
2573 cache.addPrefix(tmp_directory);
15662574 defer cache.manifest_dir.close(io);
15672575
15682576 {
1569 var ch = cache.obtain();
1570 defer ch.deinit();
2577 var man = cache.obtain();
2578 defer man.deinit();
15712579
1572 ch.hash.addBytes("1234");
1573 _ = try ch.addFilePath(.initCwd(temp_file1), null);
2580 man.hash.addBytes("1234");
2581 _ = try man.addInputPath(.{
2582 .root_dir = tmp_directory,
2583 .sub_path = temp_file1,
2584 }, .{});
15742585
1575 // There should be nothing in the cache
1576 try testing.expectEqual(false, try ch.hit(.none));
2586 var diag: Manifest.CheckDiagnostic = undefined;
2587 try testing.expectEqual(.incomplete_manifest, try man.check(&diag, .none));
15772588
1578 _ = try ch.addFilePost(temp_file2);
2589 try man.addDiscoveredPath(.{ .discovered_path = .{ .unresolved = .{
2590 .root_dir = tmp_directory,
2591 .sub_path = temp_file2,
2592 } } });
15792593
1580 digest1 = ch.final();
1581 try ch.writeManifest();
2594 digest1 = man.missDigestHex();
2595 try man.finalize();
15822596 }
15832597 {
1584 var ch = cache.obtain();
1585 defer ch.deinit();
2598 var man = cache.obtain();
2599 defer man.deinit();
15862600
1587 ch.hash.addBytes("1234");
1588 _ = try ch.addFilePath(.initCwd(temp_file1), null);
2601 man.hash.addBytes("1234");
2602 _ = try man.addInputPath(.{
2603 .root_dir = tmp_directory,
2604 .sub_path = temp_file1,
2605 }, .{});
15892606
1590 try testing.expect(try ch.hit(.none));
1591 digest2 = ch.final();
2607 var diag: Manifest.CheckDiagnostic = undefined;
2608 try testing.expect(.hit == try man.check(&diag, .none));
2609 digest2 = man.hitDigestHex();
15922610
1593 try testing.expectEqual(false, ch.have_exclusive_lock);
2611 try testing.expectEqual(false, man.have_exclusive_lock);
15942612 }
15952613 try testing.expect(mem.eql(u8, &digest1, &digest2));
15962614
......@@ -1600,26 +2618,43 @@ test "Manifest with files added after initial hash work" {
16002618 // Wait for file timestamps to tick
16012619 const initial_time2 = try testGetCurrentFileTimestamp(io, tmp.dir);
16022620 while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time2.nanoseconds) {
1603 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
2621 try Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
16042622 }
16052623
16062624 {
1607 var ch = cache.obtain();
1608 defer ch.deinit();
1609
1610 ch.hash.addBytes("1234");
1611 _ = try ch.addFilePath(.initCwd(temp_file1), null);
1612
1613 // A file that we depend on has been updated, so the cache should not contain an entry for it
1614 try testing.expectEqual(false, try ch.hit(.none));
2625 var man = cache.obtain();
2626 defer man.deinit();
2627
2628 man.hash.addBytes("1234");
2629 _ = try man.addInputPath(.{
2630 .root_dir = tmp_directory,
2631 .sub_path = temp_file1,
2632 }, .{});
2633
2634 var diag: Manifest.CheckDiagnostic = undefined;
2635 switch (try man.check(&diag, .none)) {
2636 .contents_changed => |off| {
2637 try testing.expectEqualStrings(temp_file2, off.path(man.contents.items));
2638 },
2639 else => return error.TestFailed,
2640 }
16152641
1616 _ = try ch.addFilePost(temp_file2);
2642 try man.addDiscoveredPath(.{ .discovered_path = .{ .unresolved = .{
2643 .root_dir = tmp_directory,
2644 .sub_path = temp_file2,
2645 } } });
16172646
1618 digest3 = ch.final();
2647 digest3 = man.missDigestHex();
16192648
1620 try ch.writeManifest();
2649 try man.finalize();
16212650 }
16222651
16232652 try testing.expect(!mem.eql(u8, &digest1, &digest3));
16242653 }
16252654}
2655
2656test {
2657 _ = Path;
2658 _ = Directory;
2659 _ = DepTokenizer;
2660}
lib/std/Build/Cache/DepTokenizer.zig+188-250
......@@ -1,14 +1,15 @@
11const Tokenizer = @This();
22
3index: usize = 0,
4bytes: []const u8,
5state: State = .lhs,
6
73const std = @import("std");
4const Io = std.Io;
85const testing = std.testing;
96const assert = std.debug.assert;
107const Allocator = std.mem.Allocator;
118
9index: usize = 0,
10bytes: []const u8,
11state: State = .lhs,
12
1213pub fn next(self: *Tokenizer) ?Token {
1314 var start = self.index;
1415 var must_resolve = false;
......@@ -16,10 +17,7 @@ pub fn next(self: *Tokenizer) ?Token {
1617 const char = self.bytes[self.index];
1718 switch (self.state) {
1819 .lhs => switch (char) {
19 '\t', '\n', '\r', ' ' => {
20 // silently ignore whitespace
21 self.index += 1;
22 },
20 '\t', '\n', '\r', ' ' => self.index += 1, // Ignore whitespace.
2321 else => {
2422 start = self.index;
2523 self.state = .target;
......@@ -79,7 +77,7 @@ pub fn next(self: *Tokenizer) ?Token {
7977 self.index += 1;
8078 },
8179 else => {
82 return errorIllegalChar(.expected_dollar_sign, self.index, char);
80 return errorIllegalChar(.expected_dollar, self.index, char);
8381 },
8482 },
8583 .target_colon => switch (char) {
......@@ -347,7 +345,7 @@ pub const Token = union(enum) {
347345
348346 invalid_target: IndexAndChar,
349347 bad_target_escape: IndexAndChar,
350 expected_dollar_sign: IndexAndChar,
348 expected_dollar: IndexAndChar,
351349 continuation_eol: IndexAndChar,
352350 incomplete_escape: IndexAndChar,
353351 expected_colon: IndexAndChar,
......@@ -429,48 +427,23 @@ pub const Token = union(enum) {
429427 }
430428 }
431429
432 pub fn printError(self: Token, gpa: Allocator, list: *std.ArrayList(u8)) error{OutOfMemory}!void {
433 switch (self) {
434 .target, .target_must_resolve, .prereq, .prereq_must_resolve => unreachable, // not an error
435 .incomplete_quoted_prerequisite,
436 .incomplete_target,
437 => |index_and_bytes| {
438 try list.print(gpa, "{s} '", .{self.errStr()});
439 if (self == .incomplete_target) {
440 const tmp = Token{ .target_must_resolve = index_and_bytes.bytes };
441 try tmp.resolve(gpa, list);
442 } else {
443 try printCharValues(gpa, list, index_and_bytes.bytes);
444 }
445 try list.print(gpa, "' at position {d}", .{index_and_bytes.index});
446 },
430 pub fn format(token: Token, w: *Io.Writer) Io.Writer.Error!void {
431 switch (token) {
432 .target, .target_must_resolve, .prereq, .prereq_must_resolve => return w.writeAll(@tagName(token)),
433 .incomplete_quoted_prerequisite, .incomplete_target => |info| return w.print("{t} {q} at position {d}", .{
434 token, info.bytes, info.index,
435 }),
447436 .invalid_target,
448437 .bad_target_escape,
449 .expected_dollar_sign,
438 .expected_dollar,
450439 .continuation_eol,
451440 .incomplete_escape,
452441 .expected_colon,
453 => |index_and_char| {
454 try list.appendSlice(gpa, "illegal char ");
455 try printUnderstandableChar(gpa, list, index_and_char.char);
456 try list.print(gpa, " at position {d}: {s}", .{ index_and_char.index, self.errStr() });
457 },
442 => |info| return w.print("illegal char '{f}' at position {d}: {t}", .{
443 std.zig.fmtChar(info.char), info.index, token,
444 }),
458445 }
459446 }
460
461 fn errStr(self: Token) []const u8 {
462 return switch (self) {
463 .target, .target_must_resolve, .prereq, .prereq_must_resolve => unreachable, // not an error
464 .incomplete_quoted_prerequisite => "incomplete quoted prerequisite",
465 .incomplete_target => "incomplete target",
466 .invalid_target => "invalid target",
467 .bad_target_escape => "bad target escape",
468 .expected_dollar_sign => "expecting '$'",
469 .continuation_eol => "continuation expecting end-of-line",
470 .incomplete_escape => "incomplete escape",
471 .expected_colon => "expecting ':'",
472 };
473 }
474447};
475448
476449test "empty file" {
......@@ -493,37 +466,37 @@ test "empty colon" {
493466}
494467
495468test "empty target" {
496 try depTokenizer("foo.o:", "target = {foo.o}");
469 try depTokenizer("foo.o:", "target = {\"foo.o\"}");
497470 try depTokenizer(
498471 \\foo.o:
499472 \\bar.o:
500473 \\abcd.o:
501474 ,
502 \\target = {foo.o}
503 \\target = {bar.o}
504 \\target = {abcd.o}
475 \\target = {"foo.o"}
476 \\target = {"bar.o"}
477 \\target = {"abcd.o"}
505478 );
506479}
507480
508481test "whitespace empty target" {
509 try depTokenizer("\nfoo.o:", "target = {foo.o}");
510 try depTokenizer("\rfoo.o:", "target = {foo.o}");
511 try depTokenizer("\r\nfoo.o:", "target = {foo.o}");
512 try depTokenizer(" foo.o:", "target = {foo.o}");
482 try depTokenizer("\nfoo.o:", "target = {\"foo.o\"}");
483 try depTokenizer("\rfoo.o:", "target = {\"foo.o\"}");
484 try depTokenizer("\r\nfoo.o:", "target = {\"foo.o\"}");
485 try depTokenizer(" foo.o:", "target = {\"foo.o\"}");
513486}
514487
515488test "escape empty target" {
516 try depTokenizer("\\ foo.o:", "target = { foo.o}");
517 try depTokenizer("\\#foo.o:", "target = {#foo.o}");
518 try depTokenizer("\\\\foo.o:", "target = {\\foo.o}");
519 try depTokenizer("$$foo.o:", "target = {$foo.o}");
489 try depTokenizer("\\ foo.o:", "target = {\" foo.o\"}");
490 try depTokenizer("\\#foo.o:", "target = {\"#foo.o\"}");
491 try depTokenizer("\\\\foo.o:", "target = {\"\\\\foo.o\"}");
492 try depTokenizer("$$foo.o:", "target = {\"$foo.o\"}");
520493}
521494
522495test "empty target linefeeds" {
523496 try depTokenizer("\n", "");
524497 try depTokenizer("\r\n", "");
525498
526 const expect = "target = {foo.o}";
499 const expect = "target = {\"foo.o\"}";
527500 try depTokenizer(
528501 \\foo.o:
529502 , expect);
......@@ -541,7 +514,7 @@ test "empty target linefeeds" {
541514}
542515
543516test "empty target linefeeds + continuations" {
544 const expect = "target = {foo.o}";
517 const expect = "target = {\"foo.o\"}";
545518 try depTokenizer(
546519 \\foo.o:\
547520 , expect);
......@@ -559,7 +532,7 @@ test "empty target linefeeds + continuations" {
559532}
560533
561534test "empty target linefeeds + hspace + continuations" {
562 const expect = "target = {foo.o}";
535 const expect = "target = {\"foo.o\"}";
563536 try depTokenizer(
564537 \\foo.o: \
565538 , expect);
......@@ -577,7 +550,7 @@ test "empty target linefeeds + hspace + continuations" {
577550}
578551
579552test "empty target + hspace + colon" {
580 const expect = "target = {foo.o}";
553 const expect = "target = {\"foo.o\"}";
581554
582555 try depTokenizer("foo.o :", expect);
583556 try depTokenizer("foo.o\t\t\t:", expect);
......@@ -588,8 +561,8 @@ test "empty target + hspace + colon" {
588561
589562test "prereq" {
590563 const expect =
591 \\target = {foo.o}
592 \\prereq = {foo.c}
564 \\target = {"foo.o"}
565 \\prereq = {"foo.c"}
593566 ;
594567 try depTokenizer("foo.o: foo.c", expect);
595568 try depTokenizer(
......@@ -608,9 +581,9 @@ test "prereq" {
608581
609582test "prereq continuation" {
610583 const expect =
611 \\target = {foo.o}
612 \\prereq = {foo.h}
613 \\prereq = {bar.h}
584 \\target = {"foo.o"}
585 \\prereq = {"foo.h"}
586 \\prereq = {"bar.h"}
614587 ;
615588 try depTokenizer(
616589 \\foo.o: foo.h\
......@@ -624,19 +597,19 @@ test "prereq continuation" {
624597
625598test "prereq continuation (CRLF)" {
626599 const expect =
627 \\target = {foo.o}
628 \\prereq = {foo.h}
629 \\prereq = {bar.h}
600 \\target = {"foo.o"}
601 \\prereq = {"foo.h"}
602 \\prereq = {"bar.h"}
630603 ;
631604 try depTokenizer("foo.o: foo.h\\\r\nbar.h", expect);
632605}
633606
634607test "multiple prereqs" {
635608 const expect =
636 \\target = {foo.o}
637 \\prereq = {foo.c}
638 \\prereq = {foo.h}
639 \\prereq = {bar.h}
609 \\target = {"foo.o"}
610 \\prereq = {"foo.c"}
611 \\prereq = {"foo.h"}
612 \\prereq = {"bar.h"}
640613 ;
641614 try depTokenizer("foo.o: foo.c foo.h bar.h", expect);
642615 try depTokenizer(
......@@ -680,19 +653,19 @@ test "multiple targets and prereqs" {
680653 \\ one.h two.h \
681654 \\ three.h four.h
682655 ,
683 \\target = {foo.o}
684 \\prereq = {foo.c}
685 \\target = {bar.o}
686 \\prereq = {bar.c}
687 \\prereq = {a.h}
688 \\prereq = {b.h}
689 \\prereq = {c.h}
690 \\target = {abc.o}
691 \\prereq = {abc.c}
692 \\prereq = {one.h}
693 \\prereq = {two.h}
694 \\prereq = {three.h}
695 \\prereq = {four.h}
656 \\target = {"foo.o"}
657 \\prereq = {"foo.c"}
658 \\target = {"bar.o"}
659 \\prereq = {"bar.c"}
660 \\prereq = {"a.h"}
661 \\prereq = {"b.h"}
662 \\prereq = {"c.h"}
663 \\target = {"abc.o"}
664 \\prereq = {"abc.c"}
665 \\prereq = {"one.h"}
666 \\prereq = {"two.h"}
667 \\prereq = {"three.h"}
668 \\prereq = {"four.h"}
696669 );
697670 try depTokenizer(
698671 \\ascii.o: ascii.c
......@@ -702,21 +675,21 @@ test "multiple targets and prereqs" {
702675 \\ macho.c\
703676 \\ a.h b.h c.h
704677 ,
705 \\target = {ascii.o}
706 \\prereq = {ascii.c}
707 \\target = {base64.o}
708 \\prereq = {base64.c}
709 \\prereq = {stdio.h}
710 \\target = {elf.o}
711 \\prereq = {elf.c}
712 \\prereq = {a.h}
713 \\prereq = {b.h}
714 \\prereq = {c.h}
715 \\target = {macho.o}
716 \\prereq = {macho.c}
717 \\prereq = {a.h}
718 \\prereq = {b.h}
719 \\prereq = {c.h}
678 \\target = {"ascii.o"}
679 \\prereq = {"ascii.c"}
680 \\target = {"base64.o"}
681 \\prereq = {"base64.c"}
682 \\prereq = {"stdio.h"}
683 \\target = {"elf.o"}
684 \\prereq = {"elf.c"}
685 \\prereq = {"a.h"}
686 \\prereq = {"b.h"}
687 \\prereq = {"c.h"}
688 \\target = {"macho.o"}
689 \\prereq = {"macho.c"}
690 \\prereq = {"a.h"}
691 \\prereq = {"b.h"}
692 \\prereq = {"c.h"}
720693 );
721694 try depTokenizer(
722695 \\a$$scii.o: ascii.c
......@@ -726,20 +699,20 @@ test "multiple targets and prereqs" {
726699 \\ "macho!.c" \
727700 \\ a.h b.h c.h
728701 ,
729 \\target = {a$scii.o}
730 \\prereq = {ascii.c}
731 \\target = {\base64.o}
732 \\prereq = {\base64.c}
733 \\prereq = {s t#dio.h}
734 \\target = {e\lf.o}
735 \\prereq = {e\lf.c}
736 \\prereq = {a.h$$}
737 \\prereq = {$$b.h c.h$$}
738 \\target = {macho.o}
739 \\prereq = {macho!.c}
740 \\prereq = {a.h}
741 \\prereq = {b.h}
742 \\prereq = {c.h}
702 \\target = {"a$scii.o"}
703 \\prereq = {"ascii.c"}
704 \\target = {"\\base64.o"}
705 \\prereq = {"\\base64.c"}
706 \\prereq = {"s t#dio.h"}
707 \\target = {"e\\lf.o"}
708 \\prereq = {"e\\lf.c"}
709 \\prereq = {"a.h$$"}
710 \\prereq = {"$$b.h c.h$$"}
711 \\target = {"macho.o"}
712 \\prereq = {"macho!.c"}
713 \\prereq = {"a.h"}
714 \\prereq = {"b.h"}
715 \\prereq = {"c.h"}
743716 );
744717}
745718
......@@ -750,12 +723,12 @@ test "windows quoted prereqs" {
750723 \\ "C:\Program Files (x86)\Microsoft Visual Studio\foo1.h" \
751724 \\ "C:\Program Files (x86)\Microsoft Visual Studio\foo2.h"
752725 ,
753 \\target = {c:\foo.o}
754 \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\foo.c}
755 \\target = {c:\foo2.o}
756 \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\foo2.c}
757 \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\foo1.h}
758 \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\foo2.h}
726 \\target = {"c:\\foo.o"}
727 \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\foo.c"}
728 \\target = {"c:\\foo2.o"}
729 \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\foo2.c"}
730 \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\foo1.h"}
731 \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\foo2.h"}
759732 );
760733}
761734
......@@ -779,23 +752,23 @@ test "windows mixed prereqs" {
779752 \\ "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.21.27702\lib\x64\\..\..\include\vcruntime_string.h" \
780753 \\ "C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\corecrt_wstring.h"
781754 ,
782 \\target = {cimport.o}
783 \\prereq = {C:\msys64\home\anon\project\zig\master\zig-cache\o\qhvhbUo7GU5iKyQ5mpA8TcQpncCYaQu0wwvr3ybiSTj_Dtqi1Nmcb70kfODJ2Qlg\cimport.h}
784 \\prereq = {C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\stdio.h}
785 \\prereq = {C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\corecrt.h}
786 \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.21.27702\lib\x64\\..\..\include\vcruntime.h}
787 \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.21.27702\lib\x64\\..\..\include\sal.h}
788 \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.21.27702\lib\x64\\..\..\include\concurrencysal.h}
789 \\prereq = {C:\msys64\opt\zig\lib\zig\include\vadefs.h}
790 \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.21.27702\lib\x64\\..\..\include\vadefs.h}
791 \\prereq = {C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\corecrt_wstdio.h}
792 \\prereq = {C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\corecrt_stdio_config.h}
793 \\prereq = {C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\string.h}
794 \\prereq = {C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\corecrt_memory.h}
795 \\prereq = {C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\corecrt_memcpy_s.h}
796 \\prereq = {C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\errno.h}
797 \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.21.27702\lib\x64\\..\..\include\vcruntime_string.h}
798 \\prereq = {C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\corecrt_wstring.h}
755 \\target = {"cimport.o"}
756 \\prereq = {"C:\\msys64\\home\\anon\\project\\zig\\master\\zig-cache\\o\\qhvhbUo7GU5iKyQ5mpA8TcQpncCYaQu0wwvr3ybiSTj_Dtqi1Nmcb70kfODJ2Qlg\\cimport.h"}
757 \\prereq = {"C:\\Program Files (x86)\\Windows Kits\\10\\\\Include\\10.0.17763.0\\ucrt\\stdio.h"}
758 \\prereq = {"C:\\Program Files (x86)\\Windows Kits\\10\\\\Include\\10.0.17763.0\\ucrt\\corecrt.h"}
759 \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC\\14.21.27702\\lib\\x64\\\\..\\..\\include\\vcruntime.h"}
760 \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC\\14.21.27702\\lib\\x64\\\\..\\..\\include\\sal.h"}
761 \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC\\14.21.27702\\lib\\x64\\\\..\\..\\include\\concurrencysal.h"}
762 \\prereq = {"C:\\msys64\\opt\\zig\\lib\\zig\\include\\vadefs.h"}
763 \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC\\14.21.27702\\lib\\x64\\\\..\\..\\include\\vadefs.h"}
764 \\prereq = {"C:\\Program Files (x86)\\Windows Kits\\10\\\\Include\\10.0.17763.0\\ucrt\\corecrt_wstdio.h"}
765 \\prereq = {"C:\\Program Files (x86)\\Windows Kits\\10\\\\Include\\10.0.17763.0\\ucrt\\corecrt_stdio_config.h"}
766 \\prereq = {"C:\\Program Files (x86)\\Windows Kits\\10\\\\Include\\10.0.17763.0\\ucrt\\string.h"}
767 \\prereq = {"C:\\Program Files (x86)\\Windows Kits\\10\\\\Include\\10.0.17763.0\\ucrt\\corecrt_memory.h"}
768 \\prereq = {"C:\\Program Files (x86)\\Windows Kits\\10\\\\Include\\10.0.17763.0\\ucrt\\corecrt_memcpy_s.h"}
769 \\prereq = {"C:\\Program Files (x86)\\Windows Kits\\10\\\\Include\\10.0.17763.0\\ucrt\\errno.h"}
770 \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC\\14.21.27702\\lib\\x64\\\\..\\..\\include\\vcruntime_string.h"}
771 \\prereq = {"C:\\Program Files (x86)\\Windows Kits\\10\\\\Include\\10.0.17763.0\\ucrt\\corecrt_wstring.h"}
799772 );
800773}
801774
......@@ -810,14 +783,14 @@ test "windows funky targets" {
810783 \\C:\Users\anon\$$foo.o:
811784 \\C:\Users\anon\\\ \ \ \ \ foo.o:
812785 ,
813 \\target = {C:\Users\anon\foo.o}
814 \\target = {C:\Users\anon\foo .o}
815 \\target = {C:\Users\anon\foo#.o}
816 \\target = {C:\Users\anon\foo$.o}
817 \\target = {C:\Users\anon\ foo.o}
818 \\target = {C:\Users\anon\#foo.o}
819 \\target = {C:\Users\anon\$foo.o}
820 \\target = {C:\Users\anon\ foo.o}
786 \\target = {"C:\\Users\\anon\\foo.o"}
787 \\target = {"C:\\Users\\anon\\foo .o"}
788 \\target = {"C:\\Users\\anon\\foo#.o"}
789 \\target = {"C:\\Users\\anon\\foo$.o"}
790 \\target = {"C:\\Users\\anon\\ foo.o"}
791 \\target = {"C:\\Users\\anon\\#foo.o"}
792 \\target = {"C:\\Users\\anon\\$foo.o"}
793 \\target = {"C:\\Users\\anon\\ foo.o"}
821794 );
822795}
823796
......@@ -837,13 +810,13 @@ test "windows funky prereqs" {
837810 \\ somedir\\ \\ \c.c\
838811 \\
839812 ,
840 \\target = {cimport.o}
841 \\prereq = {trailingbackslash\}
842 \\prereq = {C:\Users\John Smith\AppData\Local\zig\p\1220d14057af1a9d6dde4643293527bd5ee5099517d655251a066666a4320737ea7c\cimport.c}
843 \\prereq = {somedir\ a.c}
844 \\prereq = {somedir/ a.c}
845 \\prereq = {somedir\ b.c}
846 \\prereq = {somedir\ \ \c.c}
813 \\target = {"cimport.o"}
814 \\prereq = {"trailingbackslash\\"}
815 \\prereq = {"C:\\Users\\John Smith\\AppData\\Local\\zig\\p\\1220d14057af1a9d6dde4643293527bd5ee5099517d655251a066666a4320737ea7c\\cimport.c"}
816 \\prereq = {"somedir\\ a.c"}
817 \\prereq = {"somedir/ a.c"}
818 \\prereq = {"somedir\\ b.c"}
819 \\prereq = {"somedir\\ \\ \\c.c"}
847820 );
848821}
849822
......@@ -852,171 +825,171 @@ test "windows drive and forward slashes" {
852825 \\C:/msys64/what/zig-cache\tmp\48ac4d78dd531abd-cxa_thread_atexit.obj: \
853826 \\ C:/msys64/opt/zig3/lib/zig/libc/mingw/crt/cxa_thread_atexit.c
854827 ,
855 \\target = {C:/msys64/what/zig-cache\tmp\48ac4d78dd531abd-cxa_thread_atexit.obj}
856 \\prereq = {C:/msys64/opt/zig3/lib/zig/libc/mingw/crt/cxa_thread_atexit.c}
828 \\target = {"C:/msys64/what/zig-cache\\tmp\\48ac4d78dd531abd-cxa_thread_atexit.obj"}
829 \\prereq = {"C:/msys64/opt/zig3/lib/zig/libc/mingw/crt/cxa_thread_atexit.c"}
857830 );
858831}
859832
860833test "error incomplete escape - reverse_solidus" {
861834 try depTokenizer("\\",
862 \\ERROR: illegal char '\' at position 0: incomplete escape
835 \\ERROR: illegal char '\\' at position 0: incomplete_escape
863836 );
864837 try depTokenizer("\t\\",
865 \\ERROR: illegal char '\' at position 1: incomplete escape
838 \\ERROR: illegal char '\\' at position 1: incomplete_escape
866839 );
867840 try depTokenizer("\n\\",
868 \\ERROR: illegal char '\' at position 1: incomplete escape
841 \\ERROR: illegal char '\\' at position 1: incomplete_escape
869842 );
870843 try depTokenizer("\r\\",
871 \\ERROR: illegal char '\' at position 1: incomplete escape
844 \\ERROR: illegal char '\\' at position 1: incomplete_escape
872845 );
873846 try depTokenizer("\r\n\\",
874 \\ERROR: illegal char '\' at position 2: incomplete escape
847 \\ERROR: illegal char '\\' at position 2: incomplete_escape
875848 );
876849 try depTokenizer(" \\",
877 \\ERROR: illegal char '\' at position 1: incomplete escape
850 \\ERROR: illegal char '\\' at position 1: incomplete_escape
878851 );
879852}
880853
881854test "error incomplete escape - dollar_sign" {
882855 try depTokenizer("$",
883 \\ERROR: illegal char '$' at position 0: incomplete escape
856 \\ERROR: illegal char '$' at position 0: incomplete_escape
884857 );
885858 try depTokenizer("\t$",
886 \\ERROR: illegal char '$' at position 1: incomplete escape
859 \\ERROR: illegal char '$' at position 1: incomplete_escape
887860 );
888861 try depTokenizer("\n$",
889 \\ERROR: illegal char '$' at position 1: incomplete escape
862 \\ERROR: illegal char '$' at position 1: incomplete_escape
890863 );
891864 try depTokenizer("\r$",
892 \\ERROR: illegal char '$' at position 1: incomplete escape
865 \\ERROR: illegal char '$' at position 1: incomplete_escape
893866 );
894867 try depTokenizer("\r\n$",
895 \\ERROR: illegal char '$' at position 2: incomplete escape
868 \\ERROR: illegal char '$' at position 2: incomplete_escape
896869 );
897870 try depTokenizer(" $",
898 \\ERROR: illegal char '$' at position 1: incomplete escape
871 \\ERROR: illegal char '$' at position 1: incomplete_escape
899872 );
900873}
901874
902875test "error incomplete target" {
903876 try depTokenizer("foo.o",
904 \\ERROR: incomplete target 'foo.o' at position 0
877 \\ERROR: incomplete_target "foo.o" at position 0
905878 );
906879 try depTokenizer("\tfoo.o",
907 \\ERROR: incomplete target 'foo.o' at position 1
880 \\ERROR: incomplete_target "foo.o" at position 1
908881 );
909882 try depTokenizer("\nfoo.o",
910 \\ERROR: incomplete target 'foo.o' at position 1
883 \\ERROR: incomplete_target "foo.o" at position 1
911884 );
912885 try depTokenizer("\rfoo.o",
913 \\ERROR: incomplete target 'foo.o' at position 1
886 \\ERROR: incomplete_target "foo.o" at position 1
914887 );
915888 try depTokenizer("\r\nfoo.o",
916 \\ERROR: incomplete target 'foo.o' at position 2
889 \\ERROR: incomplete_target "foo.o" at position 2
917890 );
918891 try depTokenizer(" foo.o",
919 \\ERROR: incomplete target 'foo.o' at position 1
892 \\ERROR: incomplete_target "foo.o" at position 1
920893 );
921894
922895 try depTokenizer("\\ foo.o",
923 \\ERROR: incomplete target ' foo.o' at position 0
896 \\ERROR: incomplete_target "\\ foo.o" at position 0
924897 );
925898 try depTokenizer("\\#foo.o",
926 \\ERROR: incomplete target '#foo.o' at position 0
899 \\ERROR: incomplete_target "\\#foo.o" at position 0
927900 );
928901 try depTokenizer("\\\\foo.o",
929 \\ERROR: incomplete target '\foo.o' at position 0
902 \\ERROR: incomplete_target "\\\\foo.o" at position 0
930903 );
931904 try depTokenizer("$$foo.o",
932 \\ERROR: incomplete target '$foo.o' at position 0
905 \\ERROR: incomplete_target "$$foo.o" at position 0
933906 );
934907}
935908
936909test "error illegal char at position - bad target escape" {
937910 try depTokenizer("\\\t",
938 \\ERROR: illegal char \x09 at position 1: bad target escape
911 \\ERROR: illegal char '\t' at position 1: bad_target_escape
939912 );
940913 try depTokenizer("\\\n",
941 \\ERROR: illegal char \x0A at position 1: bad target escape
914 \\ERROR: illegal char '\n' at position 1: bad_target_escape
942915 );
943916 try depTokenizer("\\\r",
944 \\ERROR: illegal char \x0D at position 1: bad target escape
917 \\ERROR: illegal char '\r' at position 1: bad_target_escape
945918 );
946919 try depTokenizer("\\\r\n",
947 \\ERROR: illegal char \x0D at position 1: bad target escape
920 \\ERROR: illegal char '\r' at position 1: bad_target_escape
948921 );
949922}
950923
951924test "error illegal char at position - expecting dollar_sign" {
952925 try depTokenizer("$\t",
953 \\ERROR: illegal char \x09 at position 1: expecting '$'
926 \\ERROR: illegal char '\t' at position 1: expected_dollar
954927 );
955928 try depTokenizer("$\n",
956 \\ERROR: illegal char \x0A at position 1: expecting '$'
929 \\ERROR: illegal char '\n' at position 1: expected_dollar
957930 );
958931 try depTokenizer("$\r",
959 \\ERROR: illegal char \x0D at position 1: expecting '$'
932 \\ERROR: illegal char '\r' at position 1: expected_dollar
960933 );
961934 try depTokenizer("$\r\n",
962 \\ERROR: illegal char \x0D at position 1: expecting '$'
935 \\ERROR: illegal char '\r' at position 1: expected_dollar
963936 );
964937}
965938
966939test "error illegal char at position - invalid target" {
967940 try depTokenizer("foo\n.o",
968 \\ERROR: illegal char \x0A at position 3: invalid target
941 \\ERROR: illegal char '\n' at position 3: invalid_target
969942 );
970943 try depTokenizer("foo\r.o",
971 \\ERROR: illegal char \x0D at position 3: invalid target
944 \\ERROR: illegal char '\r' at position 3: invalid_target
972945 );
973946 try depTokenizer("foo\r\n.o",
974 \\ERROR: illegal char \x0D at position 3: invalid target
947 \\ERROR: illegal char '\r' at position 3: invalid_target
975948 );
976949}
977950
978951test "error target - continuation expecting end-of-line" {
979952 try depTokenizer("foo.o: \\\t",
980 \\target = {foo.o}
981 \\ERROR: illegal char \x09 at position 8: continuation expecting end-of-line
953 \\target = {"foo.o"}
954 \\ERROR: illegal char '\t' at position 8: continuation_eol
982955 );
983956 try depTokenizer("foo.o: \\ ",
984 \\target = {foo.o}
985 \\ERROR: illegal char ' ' at position 8: continuation expecting end-of-line
957 \\target = {"foo.o"}
958 \\ERROR: illegal char ' ' at position 8: continuation_eol
986959 );
987960 try depTokenizer("foo.o: \\x",
988 \\target = {foo.o}
989 \\ERROR: illegal char 'x' at position 8: continuation expecting end-of-line
961 \\target = {"foo.o"}
962 \\ERROR: illegal char 'x' at position 8: continuation_eol
990963 );
991964 try depTokenizer("foo.o: \\\x0dx",
992 \\target = {foo.o}
993 \\ERROR: illegal char 'x' at position 9: continuation expecting end-of-line
965 \\target = {"foo.o"}
966 \\ERROR: illegal char 'x' at position 9: continuation_eol
994967 );
995968}
996969
997970test "error prereq - continuation expecting end-of-line" {
998971 try depTokenizer("foo.o: foo.h\\\x0dx",
999 \\target = {foo.o}
1000 \\ERROR: illegal char 'x' at position 14: continuation expecting end-of-line
972 \\target = {"foo.o"}
973 \\ERROR: illegal char 'x' at position 14: continuation_eol
1001974 );
1002975}
1003976
1004977test "error illegal char at position - expecting colon" {
1005978 try depTokenizer("foo\t.o:",
1006 \\target = {foo}
1007 \\ERROR: illegal char '.' at position 4: expecting ':'
979 \\target = {"foo"}
980 \\ERROR: illegal char '.' at position 4: expected_colon
1008981 );
1009982 try depTokenizer("foo .o:",
1010 \\target = {foo}
1011 \\ERROR: illegal char '.' at position 4: expecting ':'
983 \\target = {"foo"}
984 \\ERROR: illegal char '.' at position 4: expected_colon
1012985 );
1013986 try depTokenizer("foo \n.o:",
1014 \\target = {foo}
1015 \\ERROR: illegal char \x0A at position 4: expecting ':'
987 \\target = {"foo"}
988 \\ERROR: illegal char '\n' at position 4: expected_colon
1016989 );
1017990 try depTokenizer("foo.o\t\n:",
1018 \\target = {foo.o}
1019 \\ERROR: illegal char \x0A at position 6: expecting ':'
991 \\target = {"foo.o"}
992 \\ERROR: illegal char '\n' at position 6: expected_colon
1020993 );
1021994}
1022995
......@@ -1028,70 +1001,35 @@ fn depTokenizer(input: []const u8, expect: []const u8) !void {
10281001
10291002 var it: Tokenizer = .{ .bytes = input };
10301003 var buffer: std.ArrayList(u8) = .empty;
1031 var resolve_buf: std.ArrayList(u8) = .empty;
1004 var aux: std.ArrayList(u8) = .empty;
10321005 var i: usize = 0;
10331006 while (it.next()) |token| {
10341007 if (i != 0) try buffer.appendSlice(arena, "\n");
10351008 switch (token) {
10361009 .target, .prereq => |bytes| {
1037 try buffer.appendSlice(arena, @tagName(token));
1038 try buffer.appendSlice(arena, " = {");
1039 for (bytes) |b| {
1040 try buffer.append(arena, printable_char_tab[b]);
1041 }
1042 try buffer.appendSlice(arena, "}");
1010 try buffer.print(arena, "{t} = {{{q}}}", .{ token, bytes });
10431011 },
10441012 .target_must_resolve => {
10451013 try buffer.appendSlice(arena, "target = {");
1046 try token.resolve(arena, &resolve_buf);
1047 for (resolve_buf.items) |b| {
1048 try buffer.append(arena, printable_char_tab[b]);
1049 }
1050 resolve_buf.items.len = 0;
1014 aux.clearRetainingCapacity();
1015 try token.resolve(arena, &aux);
1016 try buffer.print(arena, "{q}", .{aux.items});
10511017 try buffer.appendSlice(arena, "}");
10521018 },
10531019 .prereq_must_resolve => {
10541020 try buffer.appendSlice(arena, "prereq = {");
1055 try token.resolve(arena, &resolve_buf);
1056 for (resolve_buf.items) |b| {
1057 try buffer.append(arena, printable_char_tab[b]);
1058 }
1059 resolve_buf.items.len = 0;
1021 aux.clearRetainingCapacity();
1022 try token.resolve(arena, &aux);
1023 try buffer.print(arena, "{q}", .{aux.items});
10601024 try buffer.appendSlice(arena, "}");
10611025 },
10621026 else => {
1063 try buffer.appendSlice(arena, "ERROR: ");
1064 try token.printError(arena, &buffer);
1027 try buffer.print(arena, "ERROR: {f}", .{token});
10651028 break;
10661029 },
10671030 }
10681031 i += 1;
10691032 }
10701033
1071 if (std.mem.eql(u8, expect, buffer.items)) {
1072 try testing.expect(true);
1073 return;
1074 }
1075
10761034 try testing.expectEqualStrings(expect, buffer.items);
10771035}
1078
1079fn printCharValues(gpa: Allocator, list: *std.ArrayList(u8), bytes: []const u8) !void {
1080 for (bytes) |b| try list.append(gpa, printable_char_tab[b]);
1081}
1082
1083fn printUnderstandableChar(gpa: Allocator, list: *std.ArrayList(u8), char: u8) !void {
1084 if (std.ascii.isPrint(char)) {
1085 try list.print(gpa, "'{c}'", .{char});
1086 } else {
1087 try list.print(gpa, "\\x{X:0>2}", .{char});
1088 }
1089}
1090
1091// zig fmt: off
1092const printable_char_tab: [256]u8 = (
1093 "................................ !\"#$%&'()*+,-./0123456789:;<=>?" ++
1094 "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~." ++
1095 "................................................................" ++
1096 "................................................................"
1097).*;
lib/std/Build/Configuration.zig+3-4
......@@ -1873,12 +1873,11 @@ pub const PathDep = extern struct {
18731873 pkg: Package.OptionalIndex,
18741874
18751875 pub const Flags = packed struct(u32) {
1876 mode: Mode,
1876 is_directory: bool,
1877 metadata_only: bool,
18771878 base: LazyPath.Relative.Base,
1878 _: u16 = 0,
1879 _: u22 = 0,
18791880 };
1880
1881 pub const Mode = enum(u8) { directory, contents, metadata };
18821881};
18831882
18841883pub const InstallDestDir = enum(u32) {
lib/std/Build/Serialize.zig+2-1
......@@ -34,7 +34,8 @@ pub fn write(b: *std.Build, wc: *Configuration.Wip, writer: *std.Io.Writer) !voi
3434 .cwd_relative => .cwd,
3535 .relative => |r| r.base,
3636 },
37 .mode = src.mode,
37 .is_directory = src.is_directory,
38 .metadata_only = src.metadata_only,
3839 },
3940 .sub = switch (src.lazy_path) {
4041 .src_path => |sp| try wc.addString(sp.sub_path),
lib/std/Io/Reader.zig+1
......@@ -397,6 +397,7 @@ pub fn appendRemainingAligned(
397397pub const UnlimitedAllocError = Allocator.Error || ShortError;
398398
399399pub fn appendRemainingUnlimited(r: *Reader, gpa: Allocator, list: *ArrayList(u8)) UnlimitedAllocError!void {
400 list.pointer_stability.assertUnlocked();
400401 var a: std.Io.Writer.Allocating = .initOwnedSlice(gpa, list.allocatedSlice());
401402 a.writer.end = list.items.len;
402403 list.* = .empty;
lib/std/array_list.zig+3
......@@ -776,6 +776,9 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type {
776776 ///
777777 /// Asserts what the capacity is equal to the length.
778778 /// Never invalidates element pointers.
779 ///
780 /// See also:
781 /// * `shrinkToLen`
779782 pub fn toOwnedSliceAssert(self: *Self) Slice {
780783 assert(self.items.len == self.capacity);
781784 const items = self.items;
lib/std/testing.zig+3
......@@ -592,6 +592,9 @@ pub const TmpDir = struct {
592592 const random_bytes_count = 12;
593593 const sub_path_len = std.base64.url_safe.Encoder.calcSize(random_bytes_count);
594594
595 /// Deprecated.
596 pub const parent_dir_path: []const u8 = ".zig-cache" ++ std.fs.path.sep_str ++ "tmp";
597
595598 pub fn cleanup(self: *TmpDir) void {
596599 self.dir.close(io);
597600 self.parent_dir.deleteTree(io, &self.sub_path) catch {};
lib/std/zig.zig+6-6
......@@ -1838,12 +1838,12 @@ pub fn buildExeSubprocess(
18381838 var it = mem.splitScalar(u8, body, 0);
18391839 while (it.next()) |prefixed_path| {
18401840 const prefix: Server.Message.PathPrefix = @fromBackingInt(@intCast(prefixed_path[0] - 1));
1841 const sub_path = try gpa.dupe(u8, prefixed_path[1..]);
1842 var keep = false;
1843 defer if (!keep) gpa.free(sub_path);
1844 keep = man.addPrefixedPathPost(.{
1845 .prefix = @backingInt(prefix),
1846 .sub_path = sub_path,
1841 const sub_path = prefixed_path[1..];
1842 man.addDiscoveredPath(.{
1843 .discovered_path = .{ .prefixed = .{
1844 .prefix = @intCast(@backingInt(prefix)),
1845 .sub_path = sub_path,
1846 } },
18471847 }) catch |err| switch (err) {
18481848 error.Canceled, error.OutOfMemory => |e| return e,
18491849 else => |e| {
src/Compilation.zig+148-132
......@@ -701,39 +701,12 @@ pub const Path = struct {
701701 };
702702 }
703703
704 pub fn addToCacheManifestPostHit(p: Path, man: *Cache.Manifest, dirs: *const std.zig.Directories) !void {
705 comptime assert(0 == @backingInt(std.zig.Server.Message.PathPrefix.cwd));
706 comptime assert(1 == @backingInt(std.zig.Server.Message.PathPrefix.zig_lib));
707 comptime assert(2 == @backingInt(std.zig.Server.Message.PathPrefix.local_cache));
708 comptime assert(3 == @backingInt(std.zig.Server.Message.PathPrefix.global_cache));
709 comptime assert(4 == @backingInt(std.zig.Server.Message.PathPrefix.build_root));
710 comptime assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == 5);
711 const gpa = man.cache.gpa;
712 const prefixed_path: Cache.PrefixedPath = .{
713 .prefix = switch (p.root) {
714 .none => {
715 const path = try p.toAbsolute(dirs, gpa);
716 defer gpa.free(path);
717 return man.addFilePost(path);
718 },
719 .zig_lib => 1,
720 .local_cache => 2,
721 .global_cache => 3,
722 .build_root => 4,
723 },
724 .sub_path = try gpa.dupe(u8, p.sub_path),
725 };
726 var keep = false;
727 defer if (!keep) gpa.free(prefixed_path.sub_path);
728 keep = try man.addPrefixedPathPost(prefixed_path);
729 }
730
731 pub fn addToCacheManifestPostHitContents(
704 pub fn addToCacheManifestAsDiscovered(
732705 p: Path,
733706 man: *Cache.Manifest,
734707 dirs: *const std.zig.Directories,
735 bytes: []const u8,
736 stat: Cache.File.Stat,
708 contents: ?[]const u8,
709 stat: ?Cache.Manifest.Stat,
737710 ) !void {
738711 comptime assert(0 == @backingInt(std.zig.Server.Message.PathPrefix.cwd));
739712 comptime assert(1 == @backingInt(std.zig.Server.Message.PathPrefix.zig_lib));
......@@ -741,24 +714,29 @@ pub const Path = struct {
741714 comptime assert(3 == @backingInt(std.zig.Server.Message.PathPrefix.global_cache));
742715 comptime assert(4 == @backingInt(std.zig.Server.Message.PathPrefix.build_root));
743716 comptime assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == 5);
744 const gpa = man.cache.gpa;
745 const prefixed_path: Cache.PrefixedPath = .{
746 .prefix = switch (p.root) {
747 .none => {
748 const path = try p.toAbsolute(dirs, gpa);
749 defer gpa.free(path);
750 return man.addFilePostContents(path, bytes, stat);
717 try man.addDiscoveredPath(.{
718 .discovered_path = .{ .prefixed = .{
719 .prefix = switch (p.root) {
720 .none => {
721 const gpa = man.cache.gpa;
722 const path = try p.toAbsolute(dirs, gpa);
723 defer gpa.free(path);
724 return man.addDiscoveredPath(.{
725 .discovered_path = .{ .unresolved = .initCwd(path) },
726 .contents = contents,
727 .stat = stat,
728 });
729 },
730 .zig_lib => 1,
731 .local_cache => 2,
732 .global_cache => 3,
733 .build_root => 4,
751734 },
752 .zig_lib => 1,
753 .local_cache => 2,
754 .global_cache => 3,
755 .build_root => 4,
756 },
757 .sub_path = try gpa.dupe(u8, p.sub_path),
758 };
759 var keep = false;
760 defer if (!keep) gpa.free(prefixed_path.sub_path);
761 keep = try man.addPrefixedPathPostContents(prefixed_path, bytes, stat);
735 .sub_path = p.sub_path,
736 } },
737 .contents = contents,
738 .stat = stat,
739 });
762740 }
763741};
764742
......@@ -1341,19 +1319,19 @@ pub const cache_helpers = struct {
13411319 }
13421320 }
13431321
1344 pub fn hashCSource(self: *Cache.Manifest, c_source: CSourceFile) !void {
1345 _ = try self.addFilePath(.initCwd(c_source.src_path), null);
1322 pub fn hashCSource(man: *Cache.Manifest, c_source: CSourceFile) !void {
1323 _ = try man.addInputPath(.initCwd(c_source.src_path), .{});
13461324 // Hash the extra flags, with special care to call addFile for file parameters.
13471325 // TODO this logic can likely be improved by utilizing clang_options_data.zig.
13481326 const file_args = [_][]const u8{"-include"};
13491327 var arg_i: usize = 0;
13501328 while (arg_i < c_source.extra_flags.len) : (arg_i += 1) {
13511329 const arg = c_source.extra_flags[arg_i];
1352 self.hash.addBytes(arg);
1330 man.hash.addBytes(arg);
13531331 for (file_args) |file_arg| {
13541332 if (mem.eql(u8, file_arg, arg) and arg_i + 1 < c_source.extra_flags.len) {
13551333 arg_i += 1;
1356 _ = try self.addFilePath(.initCwd(c_source.extra_flags[arg_i]), null);
1334 _ = try man.addInputPath(.initCwd(c_source.extra_flags[arg_i]), .{});
13571335 }
13581336 }
13591337 }
......@@ -2835,41 +2813,25 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
28352813 man.want_shared_lock = false;
28362814 }
28372815
2838 const is_hit = man.hit(main_progress_node) catch |err| switch (err) {
2816 var diag: Cache.Manifest.CheckDiagnostic = undefined;
2817 const status = man.check(&diag, main_progress_node) catch |err| switch (err) {
28392818 error.Canceled, error.OutOfMemory => |e| return e,
2840 error.CacheCheckFailed => switch (man.diagnostic) {
2841 .none => unreachable,
2842 .manifest_create, .manifest_read, .manifest_lock => |e| return comp.setMiscFailure(
2843 .check_whole_cache,
2844 "failed to check cache: {t} {t}",
2845 .{ man.diagnostic, e },
2846 ),
2847 .file_open, .file_stat, .file_read, .file_hash => |op| {
2848 const pp = man.files.keys()[op.file_index].prefixed_path;
2849 const prefix = man.cache.prefixes()[pp.prefix];
2850 return comp.setMiscFailure(.check_whole_cache, "failed to check cache: {f}{s} {t} {t}", .{
2851 prefix, pp.sub_path, man.diagnostic, op.err,
2852 });
2853 },
2819 error.CacheCheckFailed => {
2820 return comp.setMiscFailure(.check_whole_cache, "checking cache failed: {f}", .{diag.fmt(&man)});
28542821 },
2855 error.InvalidFormat => return comp.setMiscFailure(
2856 .check_whole_cache,
2857 "failed to check cache: invalid manifest file format",
2858 .{},
2859 ),
28602822 };
2861 if (is_hit and !ignore_hit) {
2823 log.debug("{s} whole cache {f} ignore_hit={}", .{ comp.root_name, status.fmt(&man), ignore_hit });
2824 if (status == .hit and !ignore_hit) {
28622825 // In this case the cache hit contains the full set of file system inputs. Nice!
28632826 if (comp.file_system_inputs) |buf| try man.populateFileSystemInputs(buf);
28642827 if (comp.parent_whole_cache) |pwc| {
28652828 try pwc.mutex.lock(io);
28662829 defer pwc.mutex.unlock(io);
2867 try man.populateOtherManifest(pwc.manifest, pwc.prefix_map);
2830 try pwc.manifest.addDiscoveredManifest(&man, pwc.prefix_map);
28682831 }
28692832
28702833 comp.last_update_was_cache_hit = true;
2871 log.debug("CacheMode.whole cache hit for {s}", .{comp.root_name});
2872 const bin_digest = man.finalBin();
2834 const bin_digest = man.hitDigest();
28732835
28742836 comp.digest = bin_digest;
28752837
......@@ -2877,10 +2839,9 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
28772839 whole.lock = man.toOwnedLock();
28782840 return;
28792841 }
2880 log.debug("CacheMode.whole cache miss for {s}", .{comp.root_name});
28812842
28822843 if (ignore_hit) {
2883 // Okay, now set this back so that `writeManifest` will downgrade our lock later.
2844 // Okay, now set this back so that `Manifest.finalize` will downgrade our lock later.
28842845 man.want_shared_lock = true;
28852846 }
28862847
......@@ -3036,7 +2997,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
30362997 }
30372998
30382999 if (build_options.enable_debug_extensions and comp.verbose_intern_pool) {
3039 std.debug.print("intern pool stats for '{s}':\n", .{comp.root_name});
3000 std.debug.print("intern pool stats for {q}:\n", .{comp.root_name});
30403001 zcu.intern_pool.dump();
30413002 }
30423003
......@@ -3048,11 +3009,8 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
30483009
30493010 if (comp.link_depfile) |depfile_path| if (comp.bin_file) |lf| {
30503011 assert(comp.file_system_inputs != null);
3051 comp.createDepFile(depfile_path, lf.emit) catch |err| comp.setMiscFailure(
3052 .link_depfile,
3053 "unable to write linker dependency file: {t}",
3054 .{err},
3055 );
3012 comp.createDepFile(depfile_path, lf.emit) catch |err|
3013 comp.setMiscFailure(.link_depfile, "writing linker dependency file failed: {t}", .{err});
30563014 };
30573015
30583016 if (anyErrors(comp)) {
......@@ -3075,10 +3033,10 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
30753033 if (comp.parent_whole_cache) |pwc| {
30763034 try pwc.mutex.lock(io);
30773035 defer pwc.mutex.unlock(io);
3078 try man.populateOtherManifest(pwc.manifest, pwc.prefix_map);
3036 try pwc.manifest.addDiscoveredManifest(&man, pwc.prefix_map);
30793037 }
30803038
3081 const bin_digest = man.finalBin();
3039 const bin_digest = man.missDigest();
30823040 const hex_digest = Cache.binToHex(bin_digest);
30833041
30843042 // Work around windows `AccessDenied` if any files within this
......@@ -3168,7 +3126,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
31683126 }
31693127
31703128 // Failure here only means an unnecessary cache miss.
3171 man.writeManifest() catch |err| log.warn("failed to write cache manifest: {t}", .{err});
3129 man.finalize() catch |err| log.warn("failed to write cache manifest: {t}", .{err});
31723130
31733131 assert(whole.lock == null);
31743132 whole.lock = man.toOwnedLock();
......@@ -3410,7 +3368,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
34103368 try link.hashInputs(man, comp.link_inputs);
34113369
34123370 for (comp.c_objects.items) |c_object| {
3413 _ = try man.addFilePath(.initCwd(c_object.src.src_path), null);
3371 _ = try man.addInputPath(.initCwd(c_object.src.src_path), .{});
34143372 man.hash.addOptional(c_object.src.ext);
34153373 man.hash.addListOfBytes(c_object.src.extra_flags);
34163374 }
......@@ -3418,11 +3376,11 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
34183376 for (comp.win32_resources.items) |win32_resource| {
34193377 switch (win32_resource.src) {
34203378 .rc => |rc_src| {
3421 _ = try man.addFilePath(.initCwd(rc_src.src_path), null);
3379 _ = try man.addInputPath(.initCwd(rc_src.src_path), .{});
34223380 man.hash.addListOfBytes(rc_src.extra_flags);
34233381 },
34243382 .manifest => |manifest_path| {
3425 _ = try man.addFilePath(.initCwd(manifest_path), null);
3383 _ = try man.addInputPath(.initCwd(manifest_path), .{});
34263384 },
34273385 }
34283386 }
......@@ -3454,8 +3412,8 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
34543412
34553413 const opts = comp.cache_use.whole.lf_open_opts;
34563414
3457 try man.addOptionalFilePath(opts.linker_script);
3458 try man.addOptionalFilePath(opts.version_script);
3415 try man.addInputPathOptional(opts.linker_script, .{});
3416 try man.addInputPathOptional(opts.version_script, .{});
34593417 man.hash.add(opts.allow_undefined_version);
34603418 man.hash.addOptional(opts.enable_new_dtags);
34613419
......@@ -3514,7 +3472,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
35143472
35153473 // Mach-O specific stuff
35163474 try link.File.MachO.hashAddFrameworks(man, opts.frameworks);
3517 try man.addOptionalFilePath(opts.entitlements);
3475 try man.addInputPathOptional(opts.entitlements, .{});
35183476 man.hash.addOptional(opts.pagezero_size);
35193477 man.hash.addOptional(opts.headerpad_size);
35203478 man.hash.add(opts.headerpad_max_install_names);
......@@ -5123,9 +5081,13 @@ pub fn translateC(
51235081 try @import("main.zig").translateC(gpa, arena, io, argv.items, environ_map, prog_node, comp.thread_limit, &stdout);
51245082
51255083 if (out_dep_path) |dep_file_path| add_deps: {
5084 var diagnostic: Cache.Manifest.AddDiscoveredDepFileDiagnostic = undefined;
51265085 const dep_basename = fs.path.basename(dep_file_path);
51275086 // Add the files depended on to the cache system, if a dep file was emitted
5128 man.addDepFilePost(cache_tmp_dir, dep_basename) catch |err| switch (err) {
5087 man.addDiscoveredDepFile(.{
5088 .root_dir = .{ .handle = cache_tmp_dir, .path = tmp_sub_path },
5089 .sub_path = dep_basename,
5090 }, &diagnostic) catch |err| switch (err) {
51295091 error.FileNotFound => break :add_deps,
51305092 else => |e| return e,
51315093 };
......@@ -5134,14 +5096,17 @@ pub fn translateC(
51345096 .whole => |whole| if (whole.cache_manifest) |whole_cache_manifest| {
51355097 try whole.cache_manifest_mutex.lock(io);
51365098 defer whole.cache_manifest_mutex.unlock(io);
5137 try whole_cache_manifest.addDepFilePost(cache_tmp_dir, dep_basename);
5099 try whole_cache_manifest.addDiscoveredDepFile(.{
5100 .root_dir = .{ .handle = cache_tmp_dir, .path = tmp_sub_path },
5101 .sub_path = dep_basename,
5102 }, &diagnostic);
51385103 },
51395104 .incremental, .none => {},
51405105 }
51415106
51425107 // Just to save disk space, we delete the file because it is never needed again.
51435108 cache_tmp_dir.deleteFile(io, dep_basename) catch |err| {
5144 log.warn("failed to delete '{s}': {t}", .{ dep_file_path, err });
5109 log.warn("failed to delete dep file {q}: {t}", .{ dep_file_path, err });
51455110 };
51465111 }
51475112
......@@ -5149,9 +5114,9 @@ pub fn translateC(
51495114 var reader: std.Io.Reader = .fixed(stdout);
51505115 const MessageHeader = std.zig.Server.Message.Header;
51515116 const header = reader.takeStruct(MessageHeader, .little) catch |err|
5152 fatal("unable to read translate-c MessageHeader: {s}", .{@errorName(err)});
5117 fatal("unable to read translate-c MessageHeader: {t}", .{err});
51535118 const body = reader.take(header.bytes_len) catch |err|
5154 fatal("unable to read {}-byte translate-c message body: {s}", .{ header.bytes_len, @errorName(err) });
5119 fatal("unable to read {d}-byte translate-c message body: {t}", .{ header.bytes_len, err });
51555120 switch (header.tag) {
51565121 .error_bundle => {
51575122 const error_bundle = try std.zig.Server.allocErrorBundle(gpa, body);
......@@ -5161,11 +5126,11 @@ pub fn translateC(
51615126 .errors = error_bundle,
51625127 };
51635128 },
5164 else => fatal("unexpected message type received from translate-c: {s}", .{@tagName(header.tag)}),
5129 else => fatal("unexpected message type received from translate-c: {t}", .{header.tag}),
51655130 }
51665131 }
51675132
5168 const bin_digest = man.finalBin();
5133 const bin_digest = man.missDigest();
51695134 const hex_digest = Cache.binToHex(bin_digest);
51705135 const o_sub_path = "o" ++ fs.path.sep_str ++ hex_digest;
51715136
......@@ -5556,7 +5521,18 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
55565521 const target = comp.getTarget();
55575522 assert(target.ofmt != .c);
55585523 const o_ext = target.ofmt.fileExt(target.cpu.arch);
5559 const digest = if (!comp.disable_c_depfile and try man.hit(child_progress_node)) man.final() else blk: {
5524 const digest = d: {
5525 if (!comp.disable_c_depfile) {
5526 var diagnostic: Cache.Manifest.CheckDiagnostic = undefined;
5527 const status = man.check(&diagnostic, child_progress_node) catch |err| switch (err) {
5528 error.Canceled, error.OutOfMemory => |e| return e,
5529 error.CacheCheckFailed => {
5530 return comp.failCObj(c_object, "checking cache failed: {f}", .{diagnostic.fmt(&man)});
5531 },
5532 };
5533 log.debug("C object {q} cache {f}", .{ c_object.src.src_path, status.fmt(&man) });
5534 if (status == .hit) break :d man.hitDigestHex();
5535 }
55605536 var argv: std.array_list.Managed([]const u8) = .init(gpa);
55615537 defer argv.deinit();
55625538
......@@ -5683,11 +5659,11 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
56835659 // Just to save disk space, we delete the files that are never needed again.
56845660 defer if (out_diag_path) |diag_file_path| zig_cache_tmp_dir.deleteFile(io, fs.path.basename(diag_file_path)) catch |err| switch (err) {
56855661 error.FileNotFound => {}, // the file wasn't created due to an error we reported
5686 else => log.warn("failed to delete '{s}': {s}", .{ diag_file_path, @errorName(err) }),
5662 else => log.warn("failed to delete {q}: {t}", .{ diag_file_path, err }),
56875663 };
56885664 defer if (out_dep_path) |dep_file_path| zig_cache_tmp_dir.deleteFile(io, fs.path.basename(dep_file_path)) catch |err| switch (err) {
56895665 error.FileNotFound => {}, // the file wasn't created due to an error we reported
5690 else => log.warn("failed to delete '{s}': {s}", .{ dep_file_path, @errorName(err) }),
5666 else => log.warn("failed to delete {q}: {t}", .{ dep_file_path, err }),
56915667 };
56925668 if (std.process.can_spawn) {
56935669 if (comp.clang_passthrough_mode) {
......@@ -5776,8 +5752,10 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
57765752 const dep_basename = fs.path.basename(dep_file_path);
57775753
57785754 if (comp.file_system_inputs != null) {
5755 // TODO instead of this rely on passing the manifest contents directly
5756
57795757 // Use the same file size limit as the cache code does for dependency files.
5780 const dep_file_contents = try zig_cache_tmp_dir.readFileAlloc(io, dep_basename, gpa, .limited(Cache.manifest_file_size_max));
5758 const dep_file_contents = try zig_cache_tmp_dir.readFileAlloc(io, dep_basename, gpa, .unlimited);
57815759 defer gpa.free(dep_file_contents);
57825760
57835761 var str_buf: std.ArrayList(u8) = .empty;
......@@ -5792,9 +5770,8 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
57925770 try token.resolve(gpa, &str_buf);
57935771 break :p try .fromUnresolved(arena, comp.dirs, &.{str_buf.items});
57945772 },
5795 else => |err| {
5796 try err.printError(gpa, &str_buf);
5797 log.err("failed parsing {s}: {s}", .{ dep_basename, str_buf.items });
5773 else => |err_token| {
5774 log.err("failed parsing {s}: {f}", .{ dep_basename, err_token });
57985775 return error.InvalidDepFile;
57995776 },
58005777 };
......@@ -5803,13 +5780,26 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
58035780 }
58045781
58055782 // Add the files depended on to the cache system.
5806 try man.addDepFilePost(zig_cache_tmp_dir, dep_basename);
5783 var diagnostic: Cache.Manifest.AddDiscoveredDepFileDiagnostic = undefined;
5784 try man.addDiscoveredDepFile(.{
5785 .root_dir = .{
5786 .handle = zig_cache_tmp_dir,
5787 .path = "tmp",
5788 },
5789 .sub_path = dep_basename,
5790 }, &diagnostic);
58075791 switch (comp.cache_use) {
58085792 .whole => |whole| {
58095793 if (whole.cache_manifest) |whole_cache_manifest| {
58105794 try whole.cache_manifest_mutex.lock(io);
58115795 defer whole.cache_manifest_mutex.unlock(io);
5812 try whole_cache_manifest.addDepFilePost(zig_cache_tmp_dir, dep_basename);
5796 try whole_cache_manifest.addDiscoveredDepFile(.{
5797 .root_dir = .{
5798 .handle = zig_cache_tmp_dir,
5799 .path = "tmp",
5800 },
5801 .sub_path = dep_basename,
5802 }, &diagnostic);
58135803 }
58145804 },
58155805 .incremental, .none => {},
......@@ -5817,16 +5807,28 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
58175807 }
58185808
58195809 // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
5820 if (comp.disable_c_depfile) _ = try man.hit(child_progress_node);
5810 const digest = inner: {
5811 if (comp.disable_c_depfile) {
5812 var diagnostic: Cache.Manifest.CheckDiagnostic = undefined;
5813 const status = man.check(&diagnostic, child_progress_node) catch |err| switch (err) {
5814 error.OutOfMemory, error.Canceled => |e| return e,
5815 error.CacheCheckFailed => {
5816 return comp.failCObj(c_object, "checking cache failed: {f}", .{diagnostic.fmt(&man)});
5817 },
5818 };
5819 log.debug("ignored C object cache {f}", .{status.fmt(&man)});
5820 if (status == .hit) break :inner man.hitDigestHex();
5821 }
5822 break :inner man.missDigestHex();
5823 };
58215824
58225825 // Rename into place.
5823 const digest = man.final();
5824 const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest });
5826 const o_sub_path = try fs.path.join(arena, &.{ "o", &digest });
58255827 var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{});
58265828 defer o_dir.close(io);
58275829 const tmp_basename = fs.path.basename(out_obj_path);
58285830 try Io.Dir.rename(zig_cache_tmp_dir, tmp_basename, o_dir, o_basename, io);
5829 break :blk digest;
5831 break :d digest;
58305832 };
58315833
58325834 if (man.have_exclusive_lock) {
......@@ -5834,11 +5836,8 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
58345836 // possible we had a hit and the manifest is dirty, for example if the file mtime changed but
58355837 // the contents were the same, we hit the cache but the manifest is dirty and we need to update
58365838 // it to prevent doing a full file content comparison the next time around.
5837 man.writeManifest() catch |err| {
5838 log.warn("failed to write cache manifest when compiling '{s}': {s}", .{
5839 c_object.src.src_path, @errorName(err),
5840 });
5841 };
5839 man.finalize() catch |err|
5840 log.warn("failed to write cache manifest when compiling {q}: {t}", .{ c_object.src.src_path, err });
58425841 }
58435842
58445843 const o_basename = try std.fmt.allocPrint(arena, "{s}{s}", .{ o_basename_noext, o_ext });
......@@ -5900,15 +5899,23 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
59005899 // the XML data as a RT_MANIFEST resource. This means we can skip preprocessing,
59015900 // include paths, CLI options, etc.
59025901 if (win32_resource.src == .manifest) {
5903 _ = try man.addFilePath(.initCwd(src_path), null);
5902 _ = try man.addInputPath(.initCwd(src_path), .{});
59045903
59055904 const rc_basename = try std.fmt.allocPrint(arena, "{s}.rc", .{src_basename});
59065905 const res_basename = try std.fmt.allocPrint(arena, "{s}.res", .{src_basename});
59075906
5908 const digest = if (try man.hit(child_progress_node)) man.final() else blk: {
5907 var diag: Cache.Manifest.CheckDiagnostic = undefined;
5908 const status = man.check(&diag, child_progress_node) catch |err| switch (err) {
5909 error.OutOfMemory, error.Canceled => |e| return e,
5910 error.CacheCheckFailed => return comp.failWin32Resource(win32_resource, "checking cache failed: {f}", .{
5911 diag.fmt(&man),
5912 }),
5913 };
5914 log.debug("win32 resource cache {f}", .{status.fmt(&man)});
5915 const digest = if (status == .hit) man.hitDigestHex() else miss: {
59095916 // The digest only depends on the .manifest file, so we can
59105917 // get the digest now and write the .res directly to the cache
5911 const digest = man.final();
5918 const digest = man.missDigestHex();
59125919
59135920 const o_sub_path = try fs.path.join(arena, &.{ "o", &digest });
59145921 var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{});
......@@ -5970,13 +5977,12 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
59705977
59715978 try spawnZigRc(comp, win32_resource, arena, argv.items, child_progress_node);
59725979
5973 break :blk digest;
5980 break :miss digest;
59745981 };
59755982
59765983 if (man.have_exclusive_lock) {
5977 man.writeManifest() catch |err| {
5978 log.warn("failed to write cache manifest when compiling '{s}': {s}", .{ src_path, @errorName(err) });
5979 };
5984 man.finalize() catch |err|
5985 log.warn("failed to write cache manifest when compiling {q}: {t}", .{ src_path, err });
59805986 }
59815987
59825988 win32_resource.status = .{
......@@ -5993,12 +5999,20 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
59935999 // We now know that we're compiling an .rc file
59946000 const rc_src = win32_resource.src.rc;
59956001
5996 _ = try man.addFilePath(.initCwd(rc_src.src_path), null);
6002 _ = try man.addInputPath(.initCwd(rc_src.src_path), .{});
59976003 man.hash.addListOfBytes(rc_src.extra_flags);
59986004
59996005 const rc_basename_noext = src_basename[0 .. src_basename.len - fs.path.extension(src_basename).len];
60006006
6001 const digest = if (try man.hit(child_progress_node)) man.final() else blk: {
6007 var diagnostic: Cache.Manifest.CheckDiagnostic = undefined;
6008 const status = man.check(&diagnostic, child_progress_node) catch |err| switch (err) {
6009 error.OutOfMemory, error.Canceled => |e| return e,
6010 error.CacheCheckFailed => return comp.failWin32Resource(win32_resource, "checking cache failed: {f}", .{
6011 diagnostic.fmt(&man),
6012 }),
6013 };
6014 log.debug("win32 resource cache {f}", .{status.fmt(&man)});
6015 const digest = if (status == .hit) man.hitDigestHex() else miss: {
60026016 var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, "tmp", .{});
60036017 defer zig_cache_tmp_dir.close(io);
60046018
......@@ -6055,12 +6069,14 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
60556069 return comp.failWin32Resource(win32_resource, "depfile from zig rc has unexpected format", .{});
60566070 }
60576071 const dep_file_path = element.string;
6058 try man.addFilePost(dep_file_path);
6072 try man.addDiscoveredPath(.{ .discovered_path = .{ .unresolved = .initCwd(dep_file_path) } });
60596073 switch (comp.cache_use) {
60606074 .whole => |whole| if (whole.cache_manifest) |whole_cache_manifest| {
60616075 try whole.cache_manifest_mutex.lock(io);
60626076 defer whole.cache_manifest_mutex.unlock(io);
6063 try whole_cache_manifest.addFilePost(dep_file_path);
6077 try whole_cache_manifest.addDiscoveredPath(.{
6078 .discovered_path = .{ .unresolved = .initCwd(dep_file_path) },
6079 });
60646080 },
60656081 .incremental, .none => {},
60666082 }
......@@ -6068,13 +6084,13 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
60686084 }
60696085
60706086 // Rename into place.
6071 const digest = man.final();
6087 const digest = man.missDigestHex();
60726088 const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest });
60736089 var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{});
60746090 defer o_dir.close(io);
60756091 const tmp_basename = fs.path.basename(out_res_path);
60766092 try Io.Dir.rename(zig_cache_tmp_dir, tmp_basename, o_dir, res_filename, io);
6077 break :blk digest;
6093 break :miss digest;
60786094 };
60796095
60806096 if (man.have_exclusive_lock) {
......@@ -6082,8 +6098,8 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
60826098 // possible we had a hit and the manifest is dirty, for example if the file mtime changed but
60836099 // the contents were the same, we hit the cache but the manifest is dirty and we need to update
60846100 // it to prevent doing a full file content comparison the next time around.
6085 man.writeManifest() catch |err| {
6086 log.warn("failed to write cache manifest when compiling '{s}': {s}", .{ rc_src.src_path, @errorName(err) });
6101 man.finalize() catch |err| {
6102 log.warn("failed to write cache manifest when compiling {q}: {t}", .{ rc_src.src_path, err });
60876103 };
60886104 }
60896105
src/Sema.zig+4-8
......@@ -12974,7 +12974,7 @@ fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
1297412974
1297512975 const result = ef_idx.get(zcu);
1297612976 if (result.val == .none) {
12977 return sema.fail(block, operand_src, "unable to open '{s}': {s}", .{ name, @errorName(result.err.?) });
12977 return sema.fail(block, operand_src, "failed opening {q}: {t}", .{ name, result.err.? });
1297812978 }
1297912979
1298012980 return Air.internedToRef(result.val);
......@@ -15911,9 +15911,7 @@ fn zirClosureGet(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
1591115911 const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu).?;
1591215912 const tree = file.getTree(zcu) catch |err| {
1591315913 // In this case we emit a warning + a less precise source location.
15914 log.warn("unable to load {f}: {s}", .{
15915 file.path.fmt(zcu.comp), @errorName(err),
15916 });
15914 log.warn("failed loading {qf}: {t}", .{ file.path.fmt(zcu.comp), err });
1591715915 break :name null;
1591815916 };
1591915917 const node = src_node.toAbsolute(src_base_node);
......@@ -15939,9 +15937,7 @@ fn zirClosureGet(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
1593915937 const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu).?;
1594015938 const tree = file.getTree(zcu) catch |err| {
1594115939 // In this case we emit a warning + a less precise source location.
15942 log.warn("unable to load {f}: {s}", .{
15943 file.path.fmt(zcu.comp), @errorName(err),
15944 });
15940 log.warn("failed loading {qf}: {t}", .{ file.path.fmt(zcu.comp), err });
1594515941 break :name null;
1594615942 };
1594715943 const node = src_node.toAbsolute(src_base_node);
......@@ -15950,7 +15946,7 @@ fn zirClosureGet(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
1595015946 };
1595115947
1595215948 const msg = if (name) |some|
15953 try sema.errMsg(src, "'{s}' not accessible from inner function", .{some})
15949 try sema.errMsg(src, "{q} not accessible from inner function", .{some})
1595415950 else
1595515951 try sema.errMsg(src, "variable not accessible from inner function", .{});
1595615952 errdefer msg.destroy(sema.gpa);
src/Zcu.zig+2-2
......@@ -974,7 +974,7 @@ pub const File = struct {
974974 success,
975975 },
976976 /// Whether this is populated depends on `status`.
977 stat: Cache.File.Stat,
977 stat: Cache.Manifest.Stat,
978978
979979 /// Whether this file is the generated file of a "builtin" module. This matters because those
980980 /// files are generated and stored in-nemory rather than being read off-disk. The rest of the
......@@ -1246,7 +1246,7 @@ pub const EmbedFile = struct {
12461246 val: InternPool.Index,
12471247 /// If this is `null` and `val` is `.none`, the file has never been loaded.
12481248 err: ?(Io.File.OpenError || Io.File.StatError || Io.File.Reader.Error || error{UnexpectedEof}),
1249 stat: Cache.File.Stat,
1249 stat: Cache.Manifest.Stat,
12501250
12511251 pub const Index = enum(u32) {
12521252 _,
src/Zcu/PerThread.zig+22-27
......@@ -224,11 +224,12 @@ pub fn update(
224224 const result = res: {
225225 try whole.cache_manifest_mutex.lock(io);
226226 defer whole.cache_manifest_mutex.unlock(io);
227 if (file.source) |source| {
228 break :res file.path.addToCacheManifestPostHitContents(man, &comp.dirs, source, file.stat);
229 } else {
230 break :res file.path.addToCacheManifestPostHit(man, &comp.dirs);
231 }
227 break :res file.path.addToCacheManifestAsDiscovered(
228 man,
229 &comp.dirs,
230 file.source,
231 if (file.source != null) file.stat else null,
232 );
232233 };
233234 result catch |err| switch (err) {
234235 error.OutOfMemory => |e| return e,
......@@ -348,11 +349,8 @@ pub fn update(
348349 }
349350}
350351fn workerUpdateBuiltinFile(comp: *Compilation, file: *Zcu.File) void {
351 Builtin.updateFileOnDisk(file, comp) catch |err| comp.lockAndSetMiscFailure(
352 .write_builtin_zig,
353 "unable to write '{f}': {s}",
354 .{ file.path.fmt(comp), @errorName(err) },
355 );
352 Builtin.updateFileOnDisk(file, comp) catch |err|
353 comp.lockAndSetMiscFailure(.write_builtin_zig, "unable to write {qf}: {t}", .{ file.path.fmt(comp), err });
356354}
357355fn workerUpdateFile(
358356 comp: *Compilation,
......@@ -371,7 +369,9 @@ fn workerUpdateFile(
371369 const active = comp.zcu.?.activate(tid);
372370 defer active.deactivate();
373371 active.pt.updateFile(file_index, file) catch |err| {
374 active.pt.reportRetryableFileError(file_index, "unable to load '{s}': {s}", .{ std.fs.path.basename(file.path.sub_path), @errorName(err) }) catch |oom| switch (oom) {
372 active.pt.reportRetryableFileError(file_index, "unable to load {q}: {t}", .{
373 std.fs.path.basename(file.path.sub_path), err,
374 }) catch |oom| switch (oom) {
375375 error.OutOfMemory => {
376376 comp.mutex.lockUncancelable(io);
377377 defer comp.mutex.unlock(io);
......@@ -2836,26 +2836,21 @@ fn updateBuiltinModule(pt: Zcu.PerThread, opts: Builtin) Allocator.Error!struct
28362836 assert(mem.eql(u8, import_path, "std")); // the single import is of 'std'
28372837 }
28382838
2839 Builtin.updateFileOnDisk(file, comp) catch |err| comp.setMiscFailure(
2840 .write_builtin_zig,
2841 "unable to write '{f}': {s}",
2842 .{ file.path.fmt(comp), @errorName(err) },
2843 );
2839 Builtin.updateFileOnDisk(file, comp) catch |err|
2840 comp.setMiscFailure(.write_builtin_zig, "unable to write {qf}: {t}", .{ file.path.fmt(comp), err });
28442841 return .{
28452842 .file = file_index,
28462843 .module_root = mod,
28472844 };
28482845}
28492846
2847pub const EmbedFileError = error{ImportOutsideModulePath} || Allocator.Error || Io.Cancelable;
2848
28502849pub fn embedFile(
28512850 pt: Zcu.PerThread,
28522851 cur_file: *Zcu.File,
28532852 import_string: []const u8,
2854) error{
2855 OutOfMemory,
2856 Canceled,
2857 ImportOutsideModulePath,
2858}!Zcu.EmbedFile.Index {
2853) EmbedFileError!Zcu.EmbedFile.Index {
28592854 const zcu = pt.zcu;
28602855 const gpa = zcu.gpa;
28612856
......@@ -2938,7 +2933,7 @@ fn updateEmbedFileInner(
29382933 };
29392934 defer file.close(io);
29402935
2941 const stat: Cache.File.Stat = .fromFs(try file.stat(io));
2936 const stat: Cache.Manifest.Stat = .init(try file.stat(io));
29422937
29432938 if (ef.val != .none) {
29442939 const old_stat = ef.stat;
......@@ -2995,10 +2990,7 @@ fn updateEmbedFileInner(
29952990}
29962991
29972992/// Assumes that `path` is allocated into `gpa`. Takes ownership of `path` on success.
2998fn newEmbedFile(
2999 pt: Zcu.PerThread,
3000 path: Compilation.Path,
3001) !*Zcu.EmbedFile {
2993fn newEmbedFile(pt: Zcu.PerThread, path: Compilation.Path) EmbedFileError!*Zcu.EmbedFile {
30022994 const zcu = pt.zcu;
30032995 const comp = zcu.comp;
30042996 const io = comp.io;
......@@ -3035,7 +3027,10 @@ fn newEmbedFile(
30353027 try whole.cache_manifest_mutex.lock(io);
30363028 defer whole.cache_manifest_mutex.unlock(io);
30373029
3038 try path.addToCacheManifestPostHitContents(man, &comp.dirs, contents, new_file.stat);
3030 path.addToCacheManifestAsDiscovered(man, &comp.dirs, contents, new_file.stat) catch |err| switch (err) {
3031 error.FileSystemFailure => unreachable, // contents and stat are both provided
3032 else => |e| return e,
3033 };
30393034 }
30403035
30413036 return new_file;
src/libs/freebsd.zig+22-10
......@@ -458,14 +458,28 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
458458 man.hash.add(target.abi);
459459 man.hash.add(target_os_version);
460460
461 const abilists_index = try man.addFilePath(.{
461 const abilists_index = try man.addInputPath(.{
462462 .root_dir = comp.dirs.zig_lib,
463463 .sub_path = abilists_path,
464 }, abilists_max_size);
465
466 if (try man.hit(prog_node)) {
467 const digest = man.final();
464 }, .{
465 .request_contents = true,
466 });
468467
468 var diag: Cache.Manifest.CheckDiagnostic = undefined;
469 const status = man.check(&diag, prog_node) catch |err| switch (err) {
470 error.OutOfMemory, error.Canceled => |e| return e,
471 error.CacheCheckFailed => {
472 comp.lockAndSetMiscFailure(
473 .freebsd_shared_objects,
474 "compiling FreeBSD libc shared objects: checking cache failed: {f}",
475 .{diag.fmt(&man)},
476 );
477 return error.AlreadyReported;
478 },
479 };
480 log.debug("freebsd_shared_objects cache {f}", .{status.fmt(&man)});
481 if (status == .hit) {
482 const digest = man.hitDigestHex();
469483 return queueSharedObjects(comp, .{
470484 .lock = man.toOwnedLock(),
471485 .dir_path = .{
......@@ -475,7 +489,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
475489 });
476490 }
477491
478 const digest = man.final();
492 const digest = man.missDigestHex();
479493 const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });
480494
481495 var o_directory: Cache.Directory = .{
......@@ -484,7 +498,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
484498 };
485499 defer o_directory.handle.close(io);
486500
487 const abilists_contents = man.files.keys()[abilists_index].contents.?;
501 const abilists_contents = abilists_index.contents(&man);
488502 const metadata = try loadMetaData(gpa, abilists_contents);
489503 defer metadata.destroy(gpa);
490504
......@@ -980,9 +994,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
980994 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);
981995 }
982996
983 man.writeManifest() catch |err| {
984 log.warn("failed to write cache manifest for FreeBSD libc stubs: {s}", .{@errorName(err)});
985 };
997 man.finalize() catch |err| log.warn("failed to write cache manifest for FreeBSD libc stubs: {t}", .{err});
986998
987999 return queueSharedObjects(comp, .{
9881000 .lock = man.toOwnedLock(),
src/libs/glibc.zig+23-11
......@@ -698,14 +698,28 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
698698 man.hash.add(target.abi);
699699 man.hash.add(target_version);
700700
701 const abilists_index = try man.addFilePath(.{
701 const abilists_index = try man.addInputPath(.{
702702 .root_dir = comp.dirs.zig_lib,
703703 .sub_path = abilists_path,
704 }, abilists_max_size);
705
706 if (try man.hit(prog_node)) {
707 const digest = man.final();
704 }, .{
705 .request_contents = true,
706 });
708707
708 var diag: Cache.Manifest.CheckDiagnostic = undefined;
709 const status = man.check(&diag, prog_node) catch |err| switch (err) {
710 error.OutOfMemory, error.Canceled => |e| return e,
711 error.CacheCheckFailed => {
712 comp.lockAndSetMiscFailure(
713 .glibc_shared_objects,
714 "compiling glibc shared objects: checking cache failed: {f}",
715 .{diag.fmt(&man)},
716 );
717 return error.AlreadyReported;
718 },
719 };
720 log.debug("glibc_shared_objects cache {f}", .{status.fmt(&man)});
721 if (status == .hit) {
722 const digest = man.hitDigestHex();
709723 return queueSharedObjects(comp, .{
710724 .lock = man.toOwnedLock(),
711725 .dir_path = .{
......@@ -715,8 +729,8 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
715729 });
716730 }
717731
718 const digest = man.final();
719 const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });
732 const digest = man.missDigestHex();
733 const o_sub_path = try path.join(arena, &.{ "o", &digest });
720734
721735 var o_directory: Cache.Directory = .{
722736 .handle = try comp.dirs.global_cache.handle.createDirPathOpen(io, o_sub_path, .{}),
......@@ -724,7 +738,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
724738 };
725739 defer o_directory.handle.close(io);
726740
727 const abilists_contents = man.files.keys()[abilists_index].contents.?;
741 const abilists_contents = abilists_index.contents(&man);
728742 const metadata = try loadMetaData(gpa, abilists_contents);
729743 defer metadata.destroy(gpa);
730744
......@@ -1129,9 +1143,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
11291143 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);
11301144 }
11311145
1132 man.writeManifest() catch |err| {
1133 log.warn("failed to write cache manifest for glibc stubs: {s}", .{@errorName(err)});
1134 };
1146 man.finalize() catch |err| log.warn("failed to write cache manifest for glibc stubs: {t}", .{err});
11351147
11361148 return queueSharedObjects(comp, .{
11371149 .lock = man.toOwnedLock(),
src/libs/mingw.zig+14-26
......@@ -246,35 +246,24 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8, prog_node: std.P
246246 var man = cache.obtain();
247247 defer man.deinit();
248248
249 _ = try man.addFilePath(def_file_path, null);
249 _ = try man.addInputPath(def_file_path, .{});
250250
251251 const final_lib_basename = try std.fmt.allocPrint(gpa, "{s}.lib", .{lib_name});
252252 errdefer gpa.free(final_lib_basename);
253253
254 const is_hit = man.hit(prog_node) catch |err| switch (err) {
255 error.CacheCheckFailed => switch (man.diagnostic) {
256 .none => unreachable,
257 .manifest_create, .manifest_read, .manifest_lock => |e| {
258 comp.setMiscFailure(.windows_import_lib, "checking cache failed: {t} {t}", .{ man.diagnostic, e });
259 return error.AlreadyReported;
260 },
261 .file_open, .file_stat, .file_read, .file_hash => |op| {
262 const pp = man.files.keys()[op.file_index].prefixed_path;
263 const prefix = man.cache.prefixes()[pp.prefix];
264 comp.setMiscFailure(.windows_import_lib, "checking cache failed: {f}{s} {t} {t}", .{
265 prefix, pp.sub_path, man.diagnostic, op.err,
266 });
267 return error.AlreadyReported;
268 },
269 },
270 error.OutOfMemory, error.Canceled => |e| return e,
271 error.InvalidFormat => {
272 comp.setMiscFailure(.windows_import_lib, "checking cache failed: invalid manifest file format", .{});
254 var diag: Cache.Manifest.CheckDiagnostic = undefined;
255 const status = man.check(&diag, prog_node) catch |err| switch (err) {
256 error.CacheCheckFailed => {
257 comp.lockAndSetMiscFailure(.windows_import_lib, "{s} cache check failed: {f}", .{
258 final_lib_basename, diag.fmt(&man),
259 });
273260 return error.AlreadyReported;
274261 },
262 error.OutOfMemory, error.Canceled => |e| return e,
275263 };
276 if (is_hit) {
277 const digest = man.final();
264 log.debug("{s} cache {f}", .{ final_lib_basename, status.fmt(&man) });
265 if (status == .hit) {
266 const digest = man.hitDigestHex();
278267 const sub_path = try std.fs.path.join(gpa, &.{ "o", &digest, final_lib_basename });
279268 errdefer gpa.free(sub_path);
280269
......@@ -293,7 +282,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8, prog_node: std.P
293282 return crt_file_path;
294283 }
295284
296 const digest = man.final();
285 const digest = man.missDigestHex();
297286 const o_sub_path = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest });
298287 var o_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, o_sub_path, .{});
299288 defer o_dir.close(io);
......@@ -365,9 +354,8 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8, prog_node: std.P
365354 try file_writer.interface.flush();
366355 }
367356
368 man.writeManifest() catch |err| {
369 log.warn("failed to write cache manifest for DLL import {s}.lib: {s}", .{ lib_name, @errorName(err) });
370 };
357 man.finalize() catch |err|
358 log.warn("failed to write cache manifest for DLL import {s}.lib: {t}", .{ lib_name, err });
371359
372360 comp.mutex.lockUncancelable(io);
373361 defer comp.mutex.unlock(io);
src/libs/netbsd.zig+23-11
......@@ -406,14 +406,28 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
406406 man.hash.add(target.abi);
407407 man.hash.add(target_version);
408408
409 const abilists_index = try man.addFilePath(.{
409 const abilists_index = try man.addInputPath(.{
410410 .root_dir = comp.dirs.zig_lib,
411411 .sub_path = abilists_path,
412 }, abilists_max_size);
413
414 if (try man.hit(prog_node)) {
415 const digest = man.final();
412 }, .{
413 .request_contents = true,
414 });
416415
416 var diag: Cache.Manifest.CheckDiagnostic = undefined;
417 const status = man.check(&diag, prog_node) catch |err| switch (err) {
418 error.CacheCheckFailed => {
419 comp.lockAndSetMiscFailure(
420 .netbsd_shared_objects,
421 "compiling NetBSD libc shared objects: checking cache failed: {f}",
422 .{diag.fmt(&man)},
423 );
424 return error.AlreadyReported;
425 },
426 error.OutOfMemory, error.Canceled => |e| return e,
427 };
428 log.debug("netbsd_shared_objects cache {f}", .{status.fmt(&man)});
429 if (status == .hit) {
430 const digest = man.hitDigestHex();
417431 return queueSharedObjects(comp, .{
418432 .lock = man.toOwnedLock(),
419433 .dir_path = .{
......@@ -423,8 +437,8 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
423437 });
424438 }
425439
426 const digest = man.final();
427 const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });
440 const digest = man.missDigestHex();
441 const o_sub_path = try path.join(arena, &.{ "o", &digest });
428442
429443 var o_directory: Cache.Directory = .{
430444 .handle = try comp.dirs.global_cache.handle.createDirPathOpen(io, o_sub_path, .{}),
......@@ -432,7 +446,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
432446 };
433447 defer o_directory.handle.close(io);
434448
435 const abilists_contents = man.files.keys()[abilists_index].contents.?;
449 const abilists_contents = abilists_index.contents(&man);
436450 const metadata = try loadMetaData(gpa, abilists_contents);
437451 defer metadata.destroy(gpa);
438452
......@@ -641,9 +655,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
641655 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);
642656 }
643657
644 man.writeManifest() catch |err| {
645 log.warn("failed to write cache manifest for NetBSD libc stubs: {s}", .{@errorName(err)});
646 };
658 man.finalize() catch |err| log.warn("failed to write cache manifest for NetBSD libc stubs: {t}", .{err});
647659
648660 return queueSharedObjects(comp, .{
649661 .lock = man.toOwnedLock(),
src/libs/openbsd.zig+22-10
......@@ -327,14 +327,28 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
327327 man.hash.add(target.abi);
328328 man.hash.add(target_version);
329329
330 const abilists_index = try man.addFilePath(.{
330 const abilists_index = try man.addInputPath(.{
331331 .root_dir = comp.dirs.zig_lib,
332332 .sub_path = abilists_path,
333 }, abilists_max_size);
334
335 if (try man.hit(prog_node)) {
336 const digest = man.final();
333 }, .{
334 .request_contents = true,
335 });
337336
337 var diag: Cache.Manifest.CheckDiagnostic = undefined;
338 const status = man.check(&diag, prog_node) catch |err| switch (err) {
339 error.OutOfMemory, error.Canceled => |e| return e,
340 error.CacheCheckFailed => {
341 comp.lockAndSetMiscFailure(
342 .openbsd_shared_objects,
343 "compiling OpenBSD libc shared objects: checking cache failed: {f}",
344 .{diag.fmt(&man)},
345 );
346 return error.AlreadyReported;
347 },
348 };
349 log.debug("openbsd_shared_objects cache {f}", .{status.fmt(&man)});
350 if (status == .hit) {
351 const digest = man.hitDigestHex();
338352 return queueSharedObjects(comp, .{
339353 .lock = man.toOwnedLock(),
340354 .dir_path = .{
......@@ -344,7 +358,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
344358 });
345359 }
346360
347 const digest = man.final();
361 const digest = man.missDigestHex();
348362 const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });
349363
350364 var o_directory: Cache.Directory = .{
......@@ -353,7 +367,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
353367 };
354368 defer o_directory.handle.close(io);
355369
356 const abilists_contents = man.files.keys()[abilists_index].contents.?;
370 const abilists_contents = abilists_index.contents(&man);
357371 const metadata = try loadMetaData(gpa, abilists_contents);
358372 defer metadata.destroy(gpa);
359373
......@@ -562,9 +576,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
562576 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);
563577 }
564578
565 man.writeManifest() catch |err| {
566 log.warn("failed to write cache manifest for OpenBSD libc stubs: {s}", .{@errorName(err)});
567 };
579 man.finalize() catch |err| log.warn("failed to write cache manifest for OpenBSD libc stubs: {t}", .{err});
568580
569581 return queueSharedObjects(comp, .{
570582 .lock = man.toOwnedLock(),
src/link.zig+17-8
......@@ -1500,11 +1500,11 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {
15001500 base.loadInput(input) catch |err| switch (err) {
15011501 error.AlreadyReported => return, // error reported via diags
15021502 else => |e| switch (input) {
1503 .dso => |dso| diags.addParseError(dso.path, "failed to parse shared library: {s}", .{@errorName(e)}),
1504 .object => |obj| diags.addParseError(obj.path, "failed to parse object: {s}", .{@errorName(e)}),
1505 .archive => |obj| diags.addParseError(obj.path, "failed to parse archive: {s}", .{@errorName(e)}),
1506 .res => |res| diags.addParseError(res.path, "failed to parse Windows resource: {s}", .{@errorName(e)}),
1507 .dso_exact => diags.addError("failed to handle dso_exact: {s}", .{@errorName(e)}),
1503 .dso => |dso| diags.addParseError(dso.path, "failed to parse shared library: {t}", .{e}),
1504 .object => |obj| diags.addParseError(obj.path, "failed to parse object: {t}", .{e}),
1505 .archive => |obj| diags.addParseError(obj.path, "failed to parse archive: {t}", .{e}),
1506 .res => |res| diags.addParseError(res.path, "failed to parse Windows resource: {t}", .{e}),
1507 .dso_exact => diags.addError("failed to handle dso_exact: {t}", .{e}),
15081508 },
15091509 };
15101510 prog_node.completeOne();
......@@ -1925,15 +1925,24 @@ pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void {
19251925 man.hash.add(@as(@typeInfo(Input).@"union".tag_type.?, link_input));
19261926 switch (link_input) {
19271927 .object, .archive => |obj| {
1928 _ = try man.addOpenedFile(obj.path, obj.file, null);
1928 _ = try man.addInputPath(obj.path, .{
1929 .handle = .{ .file = obj.file },
1930 .request_handle = true,
1931 });
19291932 man.hash.add(obj.must_link);
19301933 man.hash.add(obj.hidden);
19311934 },
19321935 .res => |res| {
1933 _ = try man.addOpenedFile(res.path, res.file, null);
1936 _ = try man.addInputPath(res.path, .{
1937 .handle = .{ .file = res.file },
1938 .request_handle = true,
1939 });
19341940 },
19351941 .dso => |dso| {
1936 _ = try man.addOpenedFile(dso.path, dso.file, null);
1942 _ = try man.addInputPath(dso.path, .{
1943 .handle = .{ .file = dso.file },
1944 .request_handle = true,
1945 });
19371946 man.hash.add(dso.needed);
19381947 man.hash.add(dso.weak);
19391948 man.hash.add(dso.reexport);
src/link/Elf.zig+2-2
......@@ -1120,7 +1120,7 @@ fn parseDso(
11201120
11211121 const handle = dso.file;
11221122
1123 const stat = Stat.fromFs(try handle.stat(io));
1123 const stat: Stat = .init(try handle.stat(io));
11241124 var header = try SharedObject.parseHeader(gpa, io, diags, dso.path, handle, stat, target);
11251125 defer header.deinit(gpa);
11261126
......@@ -4407,7 +4407,7 @@ const mem = std.mem;
44074407const Allocator = std.mem.Allocator;
44084408const Hash = std.hash.Wyhash;
44094409const Path = std.Build.Cache.Path;
4410const Stat = std.Build.Cache.File.Stat;
4410const Stat = std.Build.Cache.Manifest.Stat;
44114411
44124412const codegen = @import("../codegen.zig");
44134413const eh_frame = @import("Elf/eh_frame.zig");
src/link/Elf/SharedObject.zig+1-1
......@@ -7,7 +7,7 @@ const elf = std.elf;
77const log = std.log.scoped(.elf);
88const mem = std.mem;
99const Path = std.Build.Cache.Path;
10const Stat = std.Build.Cache.File.Stat;
10const Stat = std.Build.Cache.Manifest.Stat;
1111const Allocator = mem.Allocator;
1212
1313const Elf = @import("../Elf.zig");
src/link/MachO.zig+1-1
......@@ -155,7 +155,7 @@ pub fn hashAddFrameworks(man: *Cache.Manifest, hm: []const Framework) !void {
155155 for (hm) |value| {
156156 man.hash.add(value.needed);
157157 man.hash.add(value.weak);
158 _ = try man.addFilePath(value.path, null);
158 _ = try man.addInputPath(value.path, .{});
159159 }
160160}
161161
src/main.zig+16-7
......@@ -107,6 +107,7 @@ const normal_usage =
107107 \\
108108 \\ env Print lib path, std path, cache directory, and version
109109 \\ help Print this help and exit
110 \\ cache-cat Print a zig-cache manifest file as zon
110111 \\ std View standard library documentation in a browser
111112 \\ libc Display native libc paths file or validate one
112113 \\ targets List available compilation targets
......@@ -242,6 +243,10 @@ const Cmd = enum {
242243 ar,
243244
244245 build,
246 @"cache-cat",
247 fetch,
248 init,
249 libc,
245250
246251 clang,
247252 @"-cc1",
......@@ -258,10 +263,7 @@ const Cmd = enum {
258263 fmt,
259264 objcopy,
260265 objdump,
261 fetch,
262 libc,
263266 std,
264 init,
265267 targets,
266268 version,
267269 env,
......@@ -351,7 +353,7 @@ fn mainArgs(
351353 dev.check(.ar_command);
352354 return process.exit(try llvmArMain(arena, args));
353355 },
354 .build, .fetch, .init, .libc => {
356 .build, .fetch, .init, .libc, .@"cache-cat" => {
355357 return jitCmd(gpa, arena, io, cmd_args, environ_map, .{
356358 .cmd_name = "maker",
357359 .root_src_path = "Maker.zig",
......@@ -3268,6 +3270,7 @@ fn buildOutputType(
32683270 };
32693271
32703272 const cwd_path = try std.zig.getResolvedCwd(io, arena);
3273 std.log.debug("cwd_path={s}", .{cwd_path});
32713274
32723275 // This `init` calls `fatal` on error.
32733276 var dirs: std.zig.Directories = .init(arena, io, .{
......@@ -4876,8 +4879,14 @@ fn cmdTranslateC(
48764879 Compilation.cache_helpers.hashCSource(&man, c_source_file) catch |err|
48774880 fatal("unable to process {q}: {t}", .{ c_source_file.src_path, err });
48784881
4879 const result: Compilation.TranslateCResult = if (try man.hit(prog_node)) .{
4880 .digest = man.finalBin(),
4882 var diag: Cache.Manifest.CheckDiagnostic = undefined;
4883 const status = man.check(&diag, prog_node) catch |err| switch (err) {
4884 error.OutOfMemory, error.Canceled => |e| return e,
4885 error.CacheCheckFailed => fatal("translate-c checking cache failed: {f}", .{diag.fmt(&man)}),
4886 };
4887 std.log.debug("translate-c cache {f}", .{status.fmt(&man)});
4888 const result: Compilation.TranslateCResult = if (status == .hit) .{
4889 .digest = man.hitDigest(),
48814890 .cache_hit = true,
48824891 .errors = std.zig.ErrorBundle.empty,
48834892 } else result: {
......@@ -4904,7 +4913,7 @@ fn cmdTranslateC(
49044913 }
49054914 }
49064915
4907 man.writeManifest() catch |err| warn("failed to write cache manifest: {t}", .{err});
4916 man.finalize() catch |err| warn("failed to write cache manifest: {t}", .{err});
49084917 break :result result;
49094918 };
49104919
test/cases/compile_errors/accessing_runtime_parameter_from_outer_function.zig+1-1
......@@ -14,5 +14,5 @@ export fn entry() void {
1414
1515// error
1616//
17// :4:24: error: 'y' not accessible from inner function
17// :4:24: error: "y" not accessible from inner function
1818// :3:9: note: crossed function definition here
test/cases/compile_errors/bad_import.zig+1-1
......@@ -4,5 +4,5 @@ const bogus = @import(
44
55// error
66//
7// bogus-does-not-exist.zig:1:1: error: unable to load 'bogus-does-not-exist.zig': FileNotFound
7// bogus-does-not-exist.zig:1:1: error: unable to load "bogus-does-not-exist.zig": FileNotFound
88// :2:5: note: file imported here
test/cases/compile_errors/embedFile_with_bogus_file.zig+1-1
......@@ -6,4 +6,4 @@ export fn entry() usize {
66
77// error
88//
9// :1:29: error: unable to open 'bogus.txt': FileNotFound
9// :1:29: error: failed opening "bogus.txt": FileNotFound
test/incremental/bad_import+2-2
......@@ -10,7 +10,7 @@ const io = std.Io.Threaded.global_single_threaded.io();
1010comptime {
1111 _ = @import("bad.zig");
1212}
13#expect_error=bad.zig:1:1: error: unable to load 'bad.zig': FileNotFound
13#expect_error=bad.zig:1:1: error: unable to load "bad.zig": FileNotFound
1414#expect_error=foo.zig:2:17: note: file imported here
1515
1616#update=change bad import
......@@ -18,7 +18,7 @@ comptime {
1818comptime {
1919 _ = @import("this_is/not_real.zig");
2020}
21#expect_error=this_is/not_real.zig:1:1: error: unable to load 'not_real.zig': FileNotFound
21#expect_error=this_is/not_real.zig:1:1: error: unable to load "not_real.zig": FileNotFound
2222#expect_error=foo.zig:2:17: note: file imported here
2323
2424#update=remove import of 'foo.zig'
test/incremental/change_embed_file+2-2
......@@ -17,7 +17,7 @@ Hello again, World!
1717
1818#update=delete file
1919#rm_file=string.txt
20#expect_error=main.zig:2:27: error: unable to open 'string.txt': FileNotFound
20#expect_error=main.zig:2:27: error: failed opening "string.txt": FileNotFound
2121
2222#update=remove reference to file
2323#file=main.zig
......@@ -37,7 +37,7 @@ pub fn main() !void {
3737 try std.Io.File.stdout().writeStreamingAll(io, string);
3838}
3939const io = std.Io.Threaded.global_single_threaded.io();
40#expect_error=main.zig:2:27: error: unable to open 'string.txt': FileNotFound
40#expect_error=main.zig:2:27: error: failed opening "string.txt": FileNotFound
4141
4242#update=recreate file
4343#file=string.txt
test/incremental/change_zon_file+2-2
......@@ -17,7 +17,7 @@ const io = std.Io.Threaded.global_single_threaded.io();
1717
1818#update=delete file
1919#rm_file=message.zon
20#expect_error=message.zon:1:1: error: unable to load 'message.zon': FileNotFound
20#expect_error=message.zon:1:1: error: unable to load "message.zon": FileNotFound
2121#expect_error=main.zig:2:37: note: file imported here
2222
2323#update=remove reference to ZON file
......@@ -28,7 +28,7 @@ pub fn main() !void {
2828 try std.Io.File.stdout().writeStreamingAll(io, "a hardcoded string\n");
2929}
3030const io = std.Io.Threaded.global_single_threaded.io();
31#expect_error=message.zon:1:1: error: unable to load 'message.zon': FileNotFound
31#expect_error=message.zon:1:1: error: unable to load "message.zon": FileNotFound
3232#expect_error=main.zig:2:37: note: file imported here
3333
3434#update=recreate ZON file
test/src/Cases.zig+2-2
......@@ -319,7 +319,7 @@ pub fn addCompile(
319319pub fn addFromDir(ctx: *Cases, dir: Io.Dir, path_from_root: []const u8, b: *std.Build) void {
320320 var current_file: []const u8 = "none";
321321 ctx.addFromDirInner(dir, path_from_root, &current_file, b) catch |err| {
322 std.debug.panicExtra(@returnAddress(), "test harness failed to process file {q}: {t}\n", .{
322 std.debug.panicExtra(@returnAddress(), "test harness failed to process file {q}: {t}", .{
323323 current_file, err,
324324 });
325325 };
......@@ -348,7 +348,7 @@ fn addFromDirInner(
348348 try filenames.append(ctx.arena, try ctx.arena.dupe(u8, entry.path));
349349 },
350350 .directory => {
351 b.dependOnDirectory(b.path(b.pathJoin(&.{ path_from_root, entry.path })));
351 b.dependOnDirectoryContents(b.path(b.pathJoin(&.{ path_from_root, entry.path })));
352352 },
353353 else => continue,
354354 }
test/tests.zig+3-3
......@@ -3339,7 +3339,7 @@ pub fn addCases(
33393339
33403340 var cases = @import("src/Cases.zig").init(gpa, arena, io);
33413341
3342 b.dependOnDirectory(b.path("test/cases"));
3342 b.dependOnDirectoryContents(b.path("test/cases"));
33433343
33443344 var dir = try b.root.openDir(io, "test/cases", .{ .iterate = true });
33453345 defer dir.close(io);
......@@ -3401,7 +3401,7 @@ pub fn addIncrementalTests(
34013401 }),
34023402 });
34033403
3404 b.dependOnDirectory(b.path("test/incremental"));
3404 b.dependOnDirectoryContents(b.path("test/incremental"));
34053405
34063406 var dir = try b.root.openDir(io, "test/incremental", .{ .iterate = true });
34073407 defer dir.close(io);
......@@ -3417,7 +3417,7 @@ pub fn addIncrementalTests(
34173417 switch (entry.kind) {
34183418 .file => {},
34193419 .directory => {
3420 b.dependOnDirectory(b.path(b.pathJoin(&.{ "test", "incremental", entry.path })));
3420 b.dependOnDirectoryContents(b.path(b.pathJoin(&.{ "test", "incremental", entry.path })));
34213421 },
34223422 else => continue,
34233423 }
tools/incr-check.zig+32-20
......@@ -152,18 +152,12 @@ pub fn main(init: std.process.Init) !void {
152152
153153 var child_args: std.ArrayList([]const u8) = .empty;
154154 try child_args.appendSlice(arena, &.{
155 resolved_zig_exe,
156 "build-exe",
157 "-fincremental",
158 "-fno-ubsan-rt",
159 "-target",
160 try target_query.zigTriple(arena),
161 "--cache-dir",
162 ".local-cache",
163 "--global-cache-dir",
164 ".global-cache",
155 resolved_zig_exe, "build-exe",
156 "-fincremental", "-fno-ubsan-rt",
157 "-target", try target_query.zigTriple(arena),
158 "--cache-dir", ".local-cache",
159 "--global-cache-dir", ".global-cache",
165160 });
166 try child_args.append(arena, "--listen=-");
167161
168162 if (opt_resolved_lib_dir) |resolved_lib_dir| {
169163 try child_args.appendSlice(arena, &.{ "--zig-lib-dir", resolved_lib_dir });
......@@ -187,6 +181,7 @@ pub fn main(init: std.process.Init) !void {
187181 for (case.modules) |mod| {
188182 try child_args.append(arena, try std.fmt.allocPrint(arena, "-M{s}={s}", .{ mod.name, mod.file }));
189183 }
184 try child_args.append(arena, "--listen=-");
190185
191186 const zig_prog_node = prog_node.start("zig", 0);
192187 defer zig_prog_node.end();
......@@ -199,15 +194,23 @@ pub fn main(init: std.process.Init) !void {
199194 resolved_zig_exe;
200195
201196 try cc_child_args.appendSlice(arena, &.{
202 resolved_cc_zig_exe,
203 "cc",
204 "-target",
205 try target_query.zigTriple(arena),
206 "-I",
207 opt_resolved_lib_dir.?, // verified earlier
197 resolved_cc_zig_exe, "build-exe",
198 "-target", try target_query.zigTriple(arena),
199 "--cache-dir", ".local-cache",
200 "--global-cache-dir", ".global-cache",
201 "-I", opt_resolved_lib_dir.?, // verified earlier
202 "-lc",
208203 });
204 }
205
206 const allow_compiler_stderr = debug_log_args.items.len != 0;
209207
210 try cc_child_args.append(arena, "-o");
208 if (allow_compiler_stderr) {
209 const cmd: std.zig.SubprocessCommand = .{
210 .argv = child_args.items,
211 .cwd = tmp_dir_path,
212 };
213 std.log.scoped(.spawn).info("{f}", .{cmd});
211214 }
212215
213216 var child = try std.process.spawn(io, .{
......@@ -233,7 +236,7 @@ pub fn main(init: std.process.Init) !void {
233236 .tmp_dir = tmp_dir,
234237 .tmp_dir_path = tmp_dir_path,
235238 .child = &child,
236 .allow_compiler_stderr = debug_log_args.items.len != 0,
239 .allow_compiler_stderr = allow_compiler_stderr,
237240 .quiet = quiet,
238241 .preserve_tmp_on_fatal = preserve_tmp,
239242 .cc_child_args = &cc_child_args,
......@@ -659,9 +662,18 @@ const Eval = struct {
659662 const child_prog_node = prog_node.start("build cbe output", 0);
660663 defer child_prog_node.end();
661664
662 try eval.cc_child_args.appendSlice(eval.arena, &.{ out_path, c_path });
665 try eval.cc_child_args.append(eval.arena, try std.fmt.allocPrint(eval.arena, "-femit-bin={s}", .{out_path}));
666 try eval.cc_child_args.append(eval.arena, c_path);
663667 defer eval.cc_child_args.items.len -= 2;
664668
669 if (eval.allow_compiler_stderr) {
670 const cmd: std.zig.SubprocessCommand = .{
671 .argv = eval.cc_child_args.items,
672 .cwd = eval.tmp_dir_path,
673 };
674 std.log.scoped(.spawn).info("{f}", .{cmd});
675 }
676
665677 const result = std.process.run(eval.arena, eval.io, .{
666678 .argv = eval.cc_child_args.items,
667679 .cwd = .{ .path = eval.tmp_dir_path },