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 {...@@ -263,8 +263,8 @@ pub fn build(b: *std.Build) !void {
263 const opt_version_string = b.option([]const u8, "version-string", "Override Zig version string. Default is to find out with git.");263 const opt_version_string = b.option([]const u8, "version-string", "Override Zig version string. Default is to find out with git.");
264 const version_slice = if (opt_version_string) |version| version else v: {264 const version_slice = if (opt_version_string) |version| version else v: {
265 if (!std.process.can_spawn) {265 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", .{});266 std.log.info("version info can be provided explicitly via \"-Dversion-string\"", .{});
267 std.process.exit(1);267 std.process.fatal("version info cannot be retrieved from git", .{});
268 }268 }
269269
270 // Ensure git version changes get picked up.270 // Ensure git version changes get picked up.
...@@ -272,12 +272,13 @@ pub fn build(b: *std.Build) !void {...@@ -272,12 +272,13 @@ pub fn build(b: *std.Build) !void {
272 const io = b.graph.io;272 const io = b.graph.io;
273 const git_file = b.root.openFile(io, ".git", .{ .allow_directory = false }) catch |err| switch (err) {273 const git_file = b.root.openFile(io, ".git", .{ .allow_directory = false }) catch |err| switch (err) {
274 error.IsDir => {274 error.IsDir => {
275 b.dependOnFileContents(b.path(".git/logs/HEAD"));275 b.dependOnFileMetadata(b.path(".git/logs/HEAD"));
276 break :git;276 break :git;
277 },277 },
278 else => |e| return e,278 else => |e| return e,
279 };279 };
280 defer git_file.close(io);280 defer git_file.close(io);
281 b.dependOnFileContents(b.path(".git"));
281 var line_buffer: ["gitdir: ".len + std.Io.Dir.max_path_bytes + 1]u8 = undefined;282 var line_buffer: ["gitdir: ".len + std.Io.Dir.max_path_bytes + 1]u8 = undefined;
282 var git_file_reader = git_file.reader(io, &line_buffer);283 var git_file_reader = git_file.reader(io, &line_buffer);
283 if (std.mem.cutPrefix(u8, std.mem.trimEnd(u8, try git_file_reader.interface.allocRemaining(284 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 {...@@ -285,7 +286,7 @@ pub fn build(b: *std.Build) !void {
285 .limited("gitdir: ".len + std.Io.Dir.max_path_bytes + "\r\n".len),286 .limited("gitdir: ".len + std.Io.Dir.max_path_bytes + "\r\n".len),
286 ), "\r\n"), "gitdir: ")) |git_dir| {287 ), "\r\n"), "gitdir: ")) |git_dir| {
287 const head_file = b.pathJoin(&.{ git_dir, "logs", "HEAD" });288 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))
289 b.graph.cwdRelativePath(head_file)290 b.graph.cwdRelativePath(head_file)
290 else291 else
291 b.path(head_file));292 b.path(head_file));
...@@ -310,8 +311,9 @@ pub fn build(b: *std.Build) !void {...@@ -310,8 +311,9 @@ pub fn build(b: *std.Build) !void {
310 0 => {311 0 => {
311 // Tagged release version (e.g. 0.10.0).312 // Tagged release version (e.g. 0.10.0).
312 if (!mem.eql(u8, git_describe, version_string)) {313 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.fatal("zig version {q} does not match Git tag {q}", .{
314 std.process.exit(1);315 version_string, git_describe,
316 });
315 }317 }
316 break :v version_string;318 break :v version_string;
317 },319 },
...@@ -324,13 +326,14 @@ pub fn build(b: *std.Build) !void {...@@ -324,13 +326,14 @@ pub fn build(b: *std.Build) !void {
324326
325 const ancestor_ver = try std.SemanticVersion.parse(tagged_ancestor);327 const ancestor_ver = try std.SemanticVersion.parse(tagged_ancestor);
326 if (zig_version.order(ancestor_ver) != .gt) {328 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 });329 std.process.fatal("zig version {f} must be greater than tagged ancestor {qf}", .{
328 std.process.exit(1);330 zig_version, ancestor_ver,
331 });
329 }332 }
330333
331 // Check that the commit hash is prefixed with a 'g' (a Git convention).334 // Check that the commit hash is prefixed with a 'g' (a Git convention).
332 if (commit_id.len < 1 or commit_id[0] != 'g') {335 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});
334 break :v version_string;337 break :v version_string;
335 }338 }
336339
...@@ -338,7 +341,7 @@ pub fn build(b: *std.Build) !void {...@@ -338,7 +341,7 @@ pub fn build(b: *std.Build) !void {
338 break :v b.fmt("{s}-dev.{s}+{s}", .{ version_string, commit_height, commit_id[1..] });341 break :v b.fmt("{s}-dev.{s}+{s}", .{ version_string, commit_height, commit_id[1..] });
339 },342 },
340 else => {343 else => {
341 std.debug.print("Unexpected `git describe` output: {s}\n", .{git_describe});344 std.log.warn("unexpected \"git describe\" output: {s}", .{git_describe});
342 break :v version_string;345 break :v version_string;
343 },346 },
344 }347 }
...@@ -354,7 +357,8 @@ pub fn build(b: *std.Build) !void {...@@ -354,7 +357,8 @@ pub fn build(b: *std.Build) !void {
354 const file_contents = cwd.readFileAlloc(io, config_h_path, arena, .limited(max_config_h_bytes)) catch unreachable;357 const file_contents = cwd.readFileAlloc(io, config_h_path, arena, .limited(max_config_h_bytes)) catch unreachable;
355 break :blk parseConfigH(b, file_contents);358 break :blk parseConfigH(b, file_contents);
356 } else {359 } 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\"", .{});
358 break :blk null;362 break :blk null;
359 }363 }
360 };364 };
lib/compiler/Maker.zig+119-16
...@@ -199,12 +199,13 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -199,12 +199,13 @@ pub fn main(init: process.Init.Minimal) !void {
199 .random_seed = parseRandomSeed(seed_arg),199 .random_seed = parseRandomSeed(seed_arg),
200 };200 };
201201
202 const cmd = stringToEnum(enum { libc, init, fetch, build }, cmd_name) orelse202 const cmd = stringToEnum(enum { libc, init, fetch, build, @"cache-cat" }, cmd_name) orelse
203 fatal("bad command name: {q}", .{cmd_name});203 fatal("bad command name: {q}", .{cmd_name});
204 switch (cmd) {204 switch (cmd) {
205 .libc => return cmdLibC(gpa, &graph, args[arg_i..]),205 .libc => return cmdLibC(gpa, &graph, args[arg_i..]),
206 .init => return cmdInit(gpa, &graph, args[arg_i..]),206 .init => return cmdInit(gpa, &graph, args[arg_i..]),
207 .fetch => return cmdFetch(gpa, &graph, args[arg_i..]),207 .fetch => return cmdFetch(gpa, &graph, args[arg_i..]),
208 .@"cache-cat" => return cmdCacheCat(gpa, &graph, args[arg_i..]),
208 .build => {},209 .build => {},
209 }210 }
210211
...@@ -702,8 +703,8 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -702,8 +703,8 @@ pub fn main(init: process.Init.Minimal) !void {
702 configure: while (true) {703 configure: while (true) {
703 // Set of files that, if modified, imply that recompiling and rerunning704 // Set of files that, if modified, imply that recompiling and rerunning
704 // configurer is needed.705 // configurer is needed.
705 var configure_source_files: Cache.Manifest.Files = .empty;706 var configure_source_files: Cache.Manifest.SelfContainedFiles = .empty;
706 defer Cache.Manifest.freeFiles(gpa, &configure_source_files);707 defer configure_source_files.deinit(gpa);
707708
708 // If this fails, we can still start the server and wait for user709 // If this fails, we can still start the server and wait for user
709 // to request a rebuild. If it returns error.FailedButCacheIntact710 // to request a rebuild. If it returns error.FailedButCacheIntact
...@@ -1043,7 +1044,7 @@ const ConfigureOptions = struct {...@@ -1043,7 +1044,7 @@ const ConfigureOptions = struct {
1043 fetch_only: bool,1044 fetch_only: bool,
1044 print_configuration: PrintConfiguration,1045 print_configuration: PrintConfiguration,
1045 forks: []Fork,1046 forks: []Fork,
1046 src_files: *Cache.Manifest.Files,1047 src_files: *Cache.Manifest.SelfContainedFiles,
1047};1048};
10481049
1049fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {1050fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
...@@ -1398,8 +1399,14 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {...@@ -1398,8 +1399,14 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
1398 defer compile_prog_node.end();1399 defer compile_prog_node.end();
13991400
1400 if (config_man) |man| {1401 if (config_man) |man| {
1401 if (try man.hit(compile_prog_node)) {1402 var diagnostic: Cache.Manifest.CheckDiagnostic = undefined;
1402 const digest = man.final();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();
1403 const path: Path = .{1410 const path: Path = .{
1404 .root_dir = graph.local_cache_root,1411 .root_dir = graph.local_cache_root,
1405 .sub_path = try arena.print("c/{s}", .{&digest}),1412 .sub_path = try arena.print("c/{s}", .{&digest}),
...@@ -1497,11 +1504,12 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {...@@ -1497,11 +1504,12 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
1497 }1504 }
14981505
1499 if (config_man) |man| for (configuration.path_deps) |path_dep| {1506 if (config_man) |man| for (configuration.path_deps) |path_dep| {
1500 switch (path_dep.flags.mode) {1507 const path = try confPathDepToCachePath(arena, graph, &configuration, path_dep);
1501 .directory => {}, // TODO1508 try man.addDiscoveredPath(.{
1502 .contents => try man.addPathPost(try confPathDepToCachePath(arena, graph, &configuration, path_dep)),1509 .discovered_path = .{ .unresolved = path },
1503 .metadata => {}, // TODO1510 .handle = if (path_dep.flags.is_directory) .{ .dir = null } else .{ .file = null },
1504 }1511 .metadata_only = path_dep.flags.metadata_only,
1512 });
1505 };1513 };
15061514
1507 // If it is poisoned, there is no point in moving it to cached1515 // If it is poisoned, there is no point in moving it to cached
...@@ -1510,7 +1518,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {...@@ -1510,7 +1518,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
1510 break :cp .{ config_tmp_path, null };1518 break :cp .{ config_tmp_path, null };
1511 } else {1519 } else {
1512 const man = config_man.?;1520 const man = config_man.?;
1513 const digest = man.final();1521 const digest = man.missDigestHex();
1514 const final_path: Path = .{1522 const final_path: Path = .{
1515 .root_dir = graph.local_cache_root,1523 .root_dir = graph.local_cache_root,
1516 .sub_path = try arena.print("c/{s}", .{&digest}),1524 .sub_path = try arena.print("c/{s}", .{&digest}),
...@@ -1541,7 +1549,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {...@@ -1541,7 +1549,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
1541 config_tmp_path, final_path, e,1549 config_tmp_path, final_path, e,
1542 });1550 });
1543 };1551 };
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});
1545 options.src_files.* = man.takeFiles();1553 options.src_files.* = man.takeFiles();
1546 break :cp .{ final_path, man.toOwnedLock() };1554 break :cp .{ final_path, man.toOwnedLock() };
1547 }1555 }
...@@ -1563,10 +1571,10 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {...@@ -1563,10 +1571,10 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
15631571
1564 const configuration = c: {1572 const configuration = c: {
1565 var file = configuration_path.root_dir.handle.openFile(io, configuration_path.sub_path, .{}) catch |err|1573 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 });
1567 defer file.close(io);1575 defer file.close(io);
1568 break :c Configuration.loadFile(arena, io, file) catch |err|1576 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 });
1570 };1578 };
1571 // Technically if the configuration is marked as poisoned, we could1579 // Technically if the configuration is marked as poisoned, we could
1572 // already delete the file now, but we leave it around in case the1580 // 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 {...@@ -1879,6 +1887,101 @@ fn cmdFetch(gpa: Allocator, graph: *Graph, args: []const []const u8) !void {
1879 return process.cleanExit(io);1887 return process.cleanExit(io);
1880}1888}
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
1882const usage_fetch =1985const usage_fetch =
1883 \\Usage: zig fetch [options] <url>1986 \\Usage: zig fetch [options] <url>
1884 \\Usage: zig fetch [options] <path>1987 \\Usage: zig fetch [options] <path>
...@@ -2220,7 +2323,7 @@ fn resolveTopLevelSteps(maker: *Maker, step_names: []const []const u8) ![]const...@@ -2220,7 +2323,7 @@ fn resolveTopLevelSteps(maker: *Maker, step_names: []const []const u8) ![]const
2220fn prepare(2323fn prepare(
2221 maker: *Maker,2324 maker: *Maker,
2222 step_indices: []const Configuration.Step.Index,2325 step_indices: []const Configuration.Step.Index,
2223 configure_source_files: *const Cache.Manifest.Files,2326 configure_source_files: *const Cache.Manifest.SelfContainedFiles,
2224) !void {2327) !void {
2225 const gpa = maker.gpa;2328 const gpa = maker.gpa;
2226 const graph = maker.graph;2329 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...@@ -726,53 +726,48 @@ pub fn handleChildProcessTerm(s: *Step, maker: *Maker, term: std.process.Child.T
726/// Prefer `cacheHitWatched` unless you already added watch inputs726/// Prefer `cacheHitWatched` unless you already added watch inputs
727/// separately from using the cache system.727/// separately from using the cache system.
728pub fn cacheHit(s: *Step, maker: *Maker, man: *Cache.Manifest, parent_progress_node: std.Progress.Node) !bool {728pub 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);729 var diag: Cache.Manifest.CheckDiagnostic = undefined;
730 return s.result_cached;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;
731}735}
732736
733/// Clears previous watch inputs, if any, and then populates watch inputs from737/// Clears previous watch inputs, if any, and then populates watch inputs from
734/// the full set of files picked up by the cache manifest.738/// the full set of files picked up by the cache manifest.
735///739///
736/// Must be accompanied with `writeManifestAndWatch`.740/// Must be accompanied with `finalizeManifestAndWatch`.
737pub fn cacheHitWatched(s: *Step, maker: *Maker, man: *Cache.Manifest, parent_progress_node: std.Progress.Node) !bool {741pub 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);742 var diag: Cache.Manifest.CheckDiagnostic = undefined;
739 s.result_cached = is_hit;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;
740 // The above call to hit() populates the manifest with files, so in case of747 // The above call to hit() populates the manifest with files, so in case of
741 // a hit, we need to populate watch inputs.748 // a hit, we need to populate watch inputs.
742 if (is_hit) try setWatchInputsFromManifest(s, maker, man);749 if (hit) try setWatchInputsFromManifest(s, maker, man);
743 return is_hit;750 return hit;
744}751}
745752
746fn failWithCacheError(753fn failWithCacheError(
747 s: *Step,754 s: *Step,
748 maker: *Maker,755 maker: *Maker,
749 man: *const Cache.Manifest,756 man: *const Cache.Manifest,
750 err: Cache.Manifest.HitError,757 diag: *const Cache.Manifest.CheckDiagnostic,
758 err: Cache.Manifest.CheckError,
751) error{ OutOfMemory, Canceled, MakeFailed } {759) error{ OutOfMemory, Canceled, MakeFailed } {
752 switch (err) {760 switch (err) {
753 error.CacheCheckFailed => switch (man.diagnostic) {761 error.CacheCheckFailed => return s.fail(maker, "checking cache failed: {f}", .{diag.fmt(man)}),
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 },
766 error.OutOfMemory, error.Canceled => |e| return e,762 error.OutOfMemory, error.Canceled => |e| return e,
767 error.InvalidFormat => return s.fail(maker, "failed checking cache: invalid manifest file format", .{}),
768 }763 }
769}764}
770765
771/// Prefer `writeManifestAndWatch` unless you already added watch inputs766/// Prefer `finalizeManifestAndWatch` unless you already added watch inputs
772/// separately from using the cache system.767/// 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 {
774 if (s.test_results.isSuccess()) {769 if (s.test_results.isSuccess()) {
775 man.writeManifest() catch |err| switch (err) {770 man.finalize() catch |err| switch (err) {
776 error.Canceled => |e| return e,771 error.Canceled => |e| return e,
777 else => |e| try s.addError(maker, "failed writing cache manifest: {t}", .{e}),772 else => |e| try s.addError(maker, "failed writing cache manifest: {t}", .{e}),
778 };773 };
...@@ -783,29 +778,29 @@ pub fn writeManifest(s: *Step, maker: *Maker, man: *Cache.Manifest) !void {...@@ -783,29 +778,29 @@ pub fn writeManifest(s: *Step, maker: *Maker, man: *Cache.Manifest) !void {
783/// the full set of files picked up by the cache manifest.778/// the full set of files picked up by the cache manifest.
784///779///
785/// Must be accompanied with `cacheHitWatched`.780/// Must be accompanied with `cacheHitWatched`.
786pub fn writeManifestAndWatch(s: *Step, maker: *Maker, man: *Cache.Manifest) !void {781pub fn finalizeManifestAndWatch(s: *Step, maker: *Maker, man: *Cache.Manifest) !void {
787 try writeManifest(s, maker, man);782 try finalizeManifest(s, maker, man);
788 try setWatchInputsFromManifest(s, maker, man);783 try setWatchInputsFromManifest(s, maker, man);
789}784}
790785
791pub fn setWatchInputsFromManifest(s: *Step, maker: *Maker, man: *Cache.Manifest) !void {786pub 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());
793}788}
794789
795pub fn setWatchInputsFromManifestFiles(790pub fn setWatchInputsFromManifestFiles(
796 s: *Step,791 s: *Step,
797 maker: *Maker,792 maker: *Maker,
798 files: *const Cache.Manifest.Files,793 scf: *const Cache.Manifest.SelfContainedFiles,
799 prefixes: []const Cache.Directory,794 prefixes: []const Cache.Directory,
800) !void {795) !void {
801 const graph = maker.graph;796 const graph = maker.graph;
802 const arena = graph.arena; // TODO don't leak into process arena797 const arena = graph.arena; // TODO don't leak into process arena
803 clearWatchInputs(s, maker);798 clearWatchInputs(s, maker);
804 for (files.keys()) |file| {799 for (scf.files.keys()) |file_offset| {
805 // The file path data is freed when the cache manifest is cleaned up at the end of `make`.800 // 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));
807 try addWatchInputFromPath(s, maker, .{802 try addWatchInputFromPath(s, maker, .{
808 .root_dir = prefixes[file.prefixed_path.prefix],803 .root_dir = prefixes[file_offset.get(scf.contents.items).flags.prefix],
809 .sub_path = Dir.path.dirname(sub_path) orelse "",804 .sub_path = Dir.path.dirname(sub_path) orelse "",
810 }, Dir.path.basename(sub_path));805 }, Dir.path.basename(sub_path));
811 }806 }
lib/compiler/Maker/Step/ConfigHeader.zig+3-3
...@@ -137,7 +137,7 @@ pub fn make(...@@ -137,7 +137,7 @@ pub fn make(
137 man.hash.addBytes(output);137 man.hash.addBytes(output);
138138
139 if (try step.cacheHit(maker, &man, progress_node)) {139 if (try step.cacheHit(maker, &man, progress_node)) {
140 const digest = man.final();140 const digest = man.hitDigestHex();
141 maker.generatedPath(conf_ch.generated_dir).* = .{141 maker.generatedPath(conf_ch.generated_dir).* = .{
142 .root_dir = cache_root,142 .root_dir = cache_root,
143 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),143 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
...@@ -145,7 +145,7 @@ pub fn make(...@@ -145,7 +145,7 @@ pub fn make(
145 return;145 return;
146 }146 }
147147
148 const digest = man.final();148 const digest = man.missDigestHex();
149149
150 // If output_path has directory parts, deal with them. Example:150 // If output_path has directory parts, deal with them. Example:
151 // output_dir is zig-cache/o/HASH151 // output_dir is zig-cache/o/HASH
...@@ -169,7 +169,7 @@ pub fn make(...@@ -169,7 +169,7 @@ pub fn make(
169 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),169 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
170 };170 };
171171
172 try step.writeManifest(maker, &man);172 try step.finalizeManifest(maker, &man);
173}173}
174174
175fn ensureAllValuesUsed(175fn ensureAllValuesUsed(
lib/compiler/Maker/Step/ObjCopy.zig+4-4
...@@ -34,7 +34,7 @@ pub fn make(...@@ -34,7 +34,7 @@ pub fn make(
34 defer man.deinit();34 defer man.deinit();
3535
36 const input_path = try maker.resolveLazyPath(arena, input_lazy_path, step_index);36 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, .{});
38 man.hash.addOptionalBytes(only_section);38 man.hash.addOptionalBytes(only_section);
39 man.hash.addOptionalBytes(opt_basename);39 man.hash.addOptionalBytes(opt_basename);
40 man.hash.addOptionalBytes(opt_debug_basename);40 man.hash.addOptionalBytes(opt_debug_basename);
...@@ -48,7 +48,7 @@ pub fn make(...@@ -48,7 +48,7 @@ pub fn make(
4848
49 if (try step.cacheHit(maker, &man, progress_node)) {49 if (try step.cacheHit(maker, &man, progress_node)) {
50 // Cache hit, skip subprocess execution.50 // Cache hit, skip subprocess execution.
51 const digest = man.final();51 const digest = man.hitDigestHex();
52 maker.generatedPath(conf_oc.output_file).* = .{52 maker.generatedPath(conf_oc.output_file).* = .{
53 .root_dir = cache_root,53 .root_dir = cache_root,
54 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),54 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
...@@ -67,7 +67,7 @@ pub fn make(...@@ -67,7 +67,7 @@ pub fn make(
6767
68 // We don't find out more input files while executing objcopy so we can68 // We don't find out more input files while executing objcopy so we can
69 // already obtain the digest and use it directly as the output path.69 // already obtain the digest and use it directly as the output path.
70 const digest = man.final();70 const digest = man.missDigestHex();
71 const dest_path: Path = .{71 const dest_path: Path = .{
72 .root_dir = cache_root,72 .root_dir = cache_root,
73 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),73 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
...@@ -165,7 +165,7 @@ pub fn make(...@@ -165,7 +165,7 @@ pub fn make(
165165
166 maker.generatedPath(conf_oc.output_file).* = dest_path;166 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) {
169 error.Canceled => |e| return e,169 error.Canceled => |e| return e,
170 else => |e| try step.addError(maker, "failed writing cache manifest: {t}", .{e}),170 else => |e| try step.addError(maker, "failed writing cache manifest: {t}", .{e}),
171 };171 };
lib/compiler/Maker/Step/Options.zig+4-4
...@@ -42,7 +42,7 @@ pub fn make(...@@ -42,7 +42,7 @@ pub fn make(
42 const lazy_path = arg.path.get(conf);42 const lazy_path = arg.path.get(conf);
43 try step.addWatchInput(maker, arena, lazy_path);43 try step.addWatchInput(maker, arena, lazy_path);
44 const arg_path = try maker.resolveLazyPath(arena, lazy_path, step_index);44 const arg_path = try maker.resolveLazyPath(arena, lazy_path, step_index);
45 _ = try man.addFilePath(arg_path, null);45 _ = try man.addInputPath(arg_path, .{});
46 try args_bytes.print(arena, "pub const {f}: []const u8 = \"{f}\";\n", .{46 try args_bytes.print(arena, "pub const {f}: []const u8 = \"{f}\";\n", .{
47 std.zig.fmtId(name), arg_path.fmtEscapeString(),47 std.zig.fmtId(name), arg_path.fmtEscapeString(),
48 });48 });
...@@ -54,7 +54,7 @@ pub fn make(...@@ -54,7 +54,7 @@ pub fn make(
54 const basename = "options.zig";54 const basename = "options.zig";
5555
56 if (try step.cacheHitWatched(maker, &man, progress_node)) {56 if (try step.cacheHitWatched(maker, &man, progress_node)) {
57 const digest = man.final();57 const digest = man.hitDigestHex();
58 maker.generatedPath(conf_options.generated_file).* = .{58 maker.generatedPath(conf_options.generated_file).* = .{
59 .root_dir = cache_root,59 .root_dir = cache_root,
60 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),60 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
...@@ -63,7 +63,7 @@ pub fn make(...@@ -63,7 +63,7 @@ pub fn make(
63 return;63 return;
64 }64 }
6565
66 const digest = man.final();66 const digest = man.missDigestHex();
67 const out_path: Cache.Path = .{67 const out_path: Cache.Path = .{
68 .root_dir = cache_root,68 .root_dir = cache_root,
69 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),69 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
...@@ -95,7 +95,7 @@ pub fn make(...@@ -95,7 +95,7 @@ pub fn make(
95 },95 },
96 };96 };
9797
98 try step.writeManifestAndWatch(maker, &man);98 try step.finalizeManifestAndWatch(maker, &man);
9999
100 maker.generatedPath(conf_options.generated_file).* = out_path;100 maker.generatedPath(conf_options.generated_file).* = out_path;
101}101}
lib/compiler/Maker/Step/Run.zig+36-20
...@@ -97,7 +97,7 @@ pub fn make(...@@ -97,7 +97,7 @@ pub fn make(
97 man.hash.add(arg.flags.make_absolute);97 man.hash.add(arg.flags.make_absolute);
98 man.hash.addBytesZ(prefix);98 man.hash.addBytesZ(prefix);
99 man.hash.addBytesZ(suffix);99 man.hash.addBytesZ(suffix);
100 _ = try man.addFilePath(file_path, null);100 _ = try man.addInputPath(file_path, .{});
101 },101 },
102 .path_directory => {102 .path_directory => {
103 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";103 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
...@@ -135,7 +135,7 @@ pub fn make(...@@ -135,7 +135,7 @@ pub fn make(
135 argv_list.appendAssumeCapacity(result.written());135 argv_list.appendAssumeCapacity(result.written());
136 man.hash.addBytesZ(prefix);136 man.hash.addBytesZ(prefix);
137 man.hash.addBytesZ(suffix);137 man.hash.addBytesZ(suffix);
138 _ = try man.addFilePath(file_path, null);138 _ = try man.addInputPath(file_path, .{});
139 },139 },
140 .artifact => {140 .artifact => {
141 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";141 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
...@@ -155,7 +155,7 @@ pub fn make(...@@ -155,7 +155,7 @@ pub fn make(
155 man.hash.add(arg.flags.make_absolute);155 man.hash.add(arg.flags.make_absolute);
156 man.hash.addBytesZ(prefix);156 man.hash.addBytesZ(prefix);
157 man.hash.addBytesZ(suffix);157 man.hash.addBytesZ(suffix);
158 _ = try man.addFilePath(file_path, null);158 _ = try man.addInputPath(file_path, .{});
159 },159 },
160 .output_file, .output_directory => {160 .output_file, .output_directory => {
161 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";161 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
...@@ -211,7 +211,7 @@ pub fn make(...@@ -211,7 +211,7 @@ pub fn make(
211 },211 },
212 .lazy_path => |lazy_path| {212 .lazy_path => |lazy_path| {
213 const file_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);213 const file_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);
214 _ = try man.addFilePath(file_path, null);214 _ = try man.addInputPath(file_path, .{});
215 },215 },
216 .none => {},216 .none => {},
217 }217 }
...@@ -240,7 +240,7 @@ pub fn make(...@@ -240,7 +240,7 @@ pub fn make(
240240
241 for (conf_run.file_inputs.slice) |lazy_path| {241 for (conf_run.file_inputs.slice) |lazy_path| {
242 const file_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);242 const file_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);
243 _ = try man.addFilePath(file_path, null);243 _ = try man.addInputPath(file_path, .{});
244 }244 }
245245
246 if (conf_run.cwd.value) |lazy_path| {246 if (conf_run.cwd.value) |lazy_path| {
...@@ -261,7 +261,7 @@ pub fn make(...@@ -261,7 +261,7 @@ pub fn make(
261261
262 if (!has_side_effects and try step.cacheHitWatched(maker, &man, progress_node)) {262 if (!has_side_effects and try step.cacheHitWatched(maker, &man, progress_node)) {
263 // Cache hit; skip running command.263 // Cache hit; skip running command.
264 const digest = man.final();264 const digest = man.hitDigestHex();
265 try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest);265 try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest);
266 try populateGeneratedPaths(maker, output_placeholders.items, cache_root, &digest);266 try populateGeneratedPaths(maker, output_placeholders.items, cache_root, &digest);
267 step.result_cached = true;267 step.result_cached = true;
...@@ -270,12 +270,12 @@ pub fn make(...@@ -270,12 +270,12 @@ pub fn make(
270270
271 if (!any_dep_files) {271 if (!any_dep_files) {
272 // We already know the final output paths; use them directly.272 // 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();
274 const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest;274 const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest;
275 try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest);275 try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest);
276 try populateGeneratedPathsCreateDirs(arena, run_index, maker, output_dir_path, output_placeholders.items, argv_list.items);276 try populateGeneratedPathsCreateDirs(arena, run_index, maker, output_dir_path, output_placeholders.items, argv_list.items);
277 try runCommand(arena, run, run_index, maker, progress_node, argv_list.items, has_side_effects, output_dir_path, null);277 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);
279 return;279 return;
280 }280 }
281281
...@@ -292,23 +292,39 @@ pub fn make(...@@ -292,23 +292,39 @@ pub fn make(
292 switch (arg.flags.tag) {292 switch (arg.flags.tag) {
293 .output_file => if (arg.flags.dep_file) {293 .output_file => if (arg.flags.dep_file) {
294 const generated_path = maker.generatedPath(arg.generated.value.?).*;294 const generated_path = maker.generatedPath(arg.generated.value.?).*;
295 const result = if (has_side_effects)295 if (has_side_effects) {
296 man.addDepFile(generated_path.root_dir.handle, generated_path.sub_path)296 var diagnostic: Cache.DepTokenizer.Token = undefined;
297 else297 man.addInputDepFile(generated_path, &diagnostic) catch |err| switch (err) {
298 man.addDepFilePost(generated_path.root_dir.handle, generated_path.sub_path);298 error.OutOfMemory, error.Canceled => |e| return e,
299 result catch |err| switch (err) {299 error.InvalidDepFile => return step.fail(maker, "failed adding dep file {f} to cache: {f}", .{
300 error.OutOfMemory, error.Canceled => |e| return e,300 generated_path, diagnostic,
301 else => |e| return step.fail(maker, "failed adding to cache the file {f}: {t}", .{301 }),
302 generated_path, e,302 else => |e| return step.fail(maker, "failed adding dep file {f} to cache: {t}", .{
303 }),303 generated_path, e,
304 };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 }
305 },321 },
306 .output_directory => continue,322 .output_directory => continue,
307 else => unreachable,323 else => unreachable,
308 }324 }
309 }325 }
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
313 const any_output = output_placeholders.items.len > 0 or329 const any_output = output_placeholders.items.len > 0 or
314 conf_run.captured_stdout.value != null or conf_run.captured_stderr.value != null;330 conf_run.captured_stdout.value != null or conf_run.captured_stderr.value != null;
...@@ -344,7 +360,7 @@ pub fn make(...@@ -344,7 +360,7 @@ pub fn make(
344 };360 };
345 }361 }
346362
347 if (!has_side_effects) try step.writeManifestAndWatch(maker, &man);363 if (!has_side_effects) try step.finalizeManifestAndWatch(maker, &man);
348364
349 try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest);365 try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest);
350 try populateGeneratedPaths(maker, output_placeholders.items, cache_root, &digest);366 try populateGeneratedPaths(maker, output_placeholders.items, cache_root, &digest);
lib/compiler/Maker/Step/WriteFile.zig+5-5
...@@ -55,7 +55,7 @@ pub fn make(...@@ -55,7 +55,7 @@ pub fn make(
55 man.hash.addBytes(copy.sub_path.slice(conf));55 man.hash.addBytes(copy.sub_path.slice(conf));
56 const src_lazy_path = copy.src_file.get(conf);56 const src_lazy_path = copy.src_file.get(conf);
57 const source_path = try maker.resolveLazyPath(arena, src_lazy_path, step_index);57 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, .{});
59 try step.addWatchInput(maker, arena, src_lazy_path);59 try step.addWatchInput(maker, arena, src_lazy_path);
60 }60 }
6161
...@@ -96,7 +96,7 @@ pub fn make(...@@ -96,7 +96,7 @@ pub fn make(
96 },96 },
97 .file => {97 .file => {
98 const entry_path = try src_dir_path.join(arena, entry.path);98 const entry_path = try src_dir_path.join(arena, entry.path);
99 _ = try man.addFilePath(entry_path, null);99 _ = try man.addInputPath(entry_path, .{});
100 total_items += 1;100 total_items += 1;
101 },101 },
102 else => continue,102 else => continue,
...@@ -105,7 +105,7 @@ pub fn make(...@@ -105,7 +105,7 @@ pub fn make(
105 }105 }
106106
107 if (try step.cacheHit(maker, &man, progress_node)) {107 if (try step.cacheHit(maker, &man, progress_node)) {
108 const digest = man.final();108 const digest = man.hitDigestHex();
109 maker.generatedPath(conf_wf.generated_directory).* = .{109 maker.generatedPath(conf_wf.generated_directory).* = .{
110 .root_dir = cache_root,110 .root_dir = cache_root,
111 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),111 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
...@@ -114,7 +114,7 @@ pub fn make(...@@ -114,7 +114,7 @@ pub fn make(
114 return;114 return;
115 }115 }
116116
117 const digest = man.final();117 const digest = man.missDigestHex();
118 const out_path: Path = .{118 const out_path: Path = .{
119 .root_dir = cache_root,119 .root_dir = cache_root,
120 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),120 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
...@@ -122,7 +122,7 @@ pub fn make(...@@ -122,7 +122,7 @@ pub fn make(
122122
123 progress_node.setEstimatedTotalItems(total_items);123 progress_node.setEstimatedTotalItems(total_items);
124 try operate(maker, step_index, open_dir_cache, out_path, progress_node);124 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
127 maker.generatedPath(conf_wf.generated_directory).* = out_path;127 maker.generatedPath(conf_wf.generated_directory).* = out_path;
128 },128 },
lib/std/Build.zig+42-6
...@@ -58,7 +58,8 @@ available_deps: AvailableDeps,...@@ -58,7 +58,8 @@ available_deps: AvailableDeps,
5858
59pub const ConfigureDependency = struct {59pub const ConfigureDependency = struct {
60 lazy_path: LazyPath,60 lazy_path: LazyPath,
61 mode: std.Build.Configuration.PathDep.Mode,61 is_directory: bool,
62 metadata_only: bool,
62};63};
6364
64pub const ReleaseMode = enum {65pub const ReleaseMode = enum {
...@@ -102,7 +103,8 @@ pub const Graph = struct {...@@ -102,7 +103,8 @@ pub const Graph = struct {
102 /// Populated by calling one of:103 /// Populated by calling one of:
103 /// * `dependOnFileContents`104 /// * `dependOnFileContents`
104 /// * `dependOnFileMetadata`105 /// * `dependOnFileMetadata`
105 /// * `dependOnDirectory`106 /// * `dependOnDirectoryContents`
107 /// * `dependOnDirectoryMetadata`
106 configure_dependencies: ArrayList(ConfigureDependency) = .empty,108 configure_dependencies: ArrayList(ConfigureDependency) = .empty,
107109
108 /// If the cache is poisoned means that the **configure logic** had side110 /// 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 {...@@ -2602,7 +2604,8 @@ pub fn dependOnFileContents(b: *Build, lazy_path: LazyPath) void {
2602 const graph = b.graph;2604 const graph = b.graph;
2603 graph.configure_dependencies.append(graph.arena, .{2605 graph.configure_dependencies.append(graph.arena, .{
2604 .lazy_path = lazy_path.dupe(graph),2606 .lazy_path = lazy_path.dupe(graph),
2605 .mode = .contents,2607 .is_directory = false,
2608 .metadata_only = false,
2606 }) catch @panic("OOM");2609 }) catch @panic("OOM");
2607}2610}
26082611
...@@ -2627,7 +2630,8 @@ pub fn dependOnFileMetadata(b: *Build, lazy_path: LazyPath) void {...@@ -2627,7 +2630,8 @@ pub fn dependOnFileMetadata(b: *Build, lazy_path: LazyPath) void {
2627 const graph = b.graph;2630 const graph = b.graph;
2628 graph.configure_dependencies.append(graph.arena, .{2631 graph.configure_dependencies.append(graph.arena, .{
2629 .lazy_path = lazy_path.dupe(graph),2632 .lazy_path = lazy_path.dupe(graph),
2630 .mode = .metadata,2633 .is_directory = false,
2634 .metadata_only = true,
2631 }) catch @panic("OOM");2635 }) catch @panic("OOM");
2632}2636}
26332637
...@@ -2636,6 +2640,35 @@ pub fn dependOnFileMetadata(b: *Build, lazy_path: LazyPath) void {...@@ -2636,6 +2640,35 @@ pub fn dependOnFileMetadata(b: *Build, lazy_path: LazyPath) void {
2636/// This is an alternative to `Graph.poisonCache` that avoids making every invocation2640/// This is an alternative to `Graph.poisonCache` that avoids making every invocation
2637/// of `zig build` into a cache miss.2641/// of `zig build` into a cache miss.
2638///2642///
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///
2639/// If any file is created, deleted, or renamed in this directory, the2672/// If any file is created, deleted, or renamed in this directory, the
2640/// configure phase will be repeated.2673/// configure phase will be repeated.
2641///2674///
...@@ -2646,12 +2679,15 @@ pub fn dependOnFileMetadata(b: *Build, lazy_path: LazyPath) void {...@@ -2646,12 +2679,15 @@ pub fn dependOnFileMetadata(b: *Build, lazy_path: LazyPath) void {
2646///2679///
2647/// If the directory would be inside one of the search prefixes, then the dependency2680/// If the directory would be inside one of the search prefixes, then the dependency
2648/// cannot be tracked; `Graph.poisonCache` must be used instead.2681/// 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 {
2650 validateConfigureDependency(lazy_path);2685 validateConfigureDependency(lazy_path);
2651 const graph = b.graph;2686 const graph = b.graph;
2652 graph.configure_dependencies.append(graph.arena, .{2687 graph.configure_dependencies.append(graph.arena, .{
2653 .lazy_path = lazy_path.dupe(graph),2688 .lazy_path = lazy_path.dupe(graph),
2654 .mode = .directory,2689 .is_directory = true,
2690 .metadata_only = true,
2655 }) catch @panic("OOM");2691 }) catch @panic("OOM");
2656}2692}
26572693
lib/std/Build/Cache.zig+1920-885
...@@ -48,8 +48,7 @@ pub fn obtain(cache: *Cache) Manifest {...@@ -48,8 +48,7 @@ pub fn obtain(cache: *Cache) Manifest {
48 .cache = cache,48 .cache = cache,
49 .hash = cache.hash,49 .hash = cache.hash,
50 .manifest_file = null,50 .manifest_file = null,
51 .manifest_dirty = false,51 .state = .input,
52 .hex_digest = undefined,
53 };52 };
54}53}
5554
...@@ -57,71 +56,66 @@ pub fn prefixes(cache: *const Cache) []const Directory {...@@ -57,71 +56,66 @@ pub fn prefixes(cache: *const Cache) []const Directory {
57 return cache.prefixes_buffer[0..cache.prefixes_len];56 return cache.prefixes_buffer[0..cache.prefixes_len];
58}57}
5958
60pub const PrefixedPath = struct {59const PrefixIndex = u6;
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 }
6760
68 fn hash(pp: PrefixedPath) u32 {61const PrefixedPath = struct {
69 return @truncate(std.hash.Wyhash.hash(pp.prefix, pp.sub_path));62 prefix: PrefixIndex,
70 }63 sub_path: []const u8,
71};64};
7265
73fn findPrefixPath(cache: *const Cache, path: Path) !PrefixedPath {66fn appendPrefixedPath(cache: *const Cache, contents: *std.ArrayList(u8), prefixed_path: PrefixedPath) !PrefixIndex {
74 const gpa = cache.gpa;67 const end = contents.items.len + prefixed_path.sub_path.len;
75 const resolved_path = try std.fs.path.resolve(gpa, &.{68 const needed_alignment = @alignOf(Manifest.File) - (end % @alignOf(Manifest.File));
76 cache.cwd, path.root_dir.path orelse ".", path.subPathOrDot(),69 assert(needed_alignment >= 1); // Always need at least a null byte.
77 });70 // Extra +1 here is due to the requirement to keep at least 1 unused capacity in `Manifest.contents` at all times.
78 errdefer gpa.free(resolved_path);71 try contents.ensureTotalCapacity(cache.gpa, end + needed_alignment + 1);
79 return findPrefixResolved(cache, resolved_path);72 contents.appendSliceAssumeCapacity(prefixed_path.sub_path);
80}73 contents.appendNTimesAssumeCapacity(0, needed_alignment);
8174 return prefixed_path.prefix;
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);
87}75}
8876
89/// Takes ownership of `resolved_path` on success.77fn resolveAppendPath(cache: *const Cache, contents: *std.ArrayList(u8), path: Path) !PrefixIndex {
90fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
91 const gpa = cache.gpa;78 const gpa = cache.gpa;
92 const cwd = cache.cwd;79 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
93 for (cache.prefixes(), 0..) |prefix, i| {88 for (cache.prefixes(), 0..) |prefix, i| {
94 const p = prefix.path orelse continue;89 const pp = prefix.path orelse continue;
95 const sub_path = getPrefixSubpath(gpa, cwd, p, resolved_path) catch |err| switch (err) {90 contents.shrinkRetainingCapacity(path_start);
96 error.NotASubPath => continue,91 try std.fs.path.relativeAppend(gpa, contents, cwd, null, pp, resolved_path);
97 else => |e| return e,92 const relative = contents.items[path_start..];
98 };93
99 // Free the resolved path since we're not going to return it94 var component_iterator: std.fs.path.NativeComponentIterator = .init(relative);
100 gpa.free(resolved_path);95 if (component_iterator.root() != null) continue;
101 return .{96 const first_component = component_iterator.first();
102 .prefix = @intCast(i),97 if (first_component != null and mem.eql(u8, first_component.?.name, "..")) continue;
103 .sub_path = sub_path,98
104 };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);
105 }107 }
106108
107 return .{109 contents.shrinkRetainingCapacity(path_start);
108 .prefix = 0,110 try contents.appendSlice(gpa, resolved_path);
109 .sub_path = resolved_path,
110 };
111}
112111
113fn getPrefixSubpath(gpa: Allocator, cwd: []const u8, prefix: []const u8, path: []u8) ![]u8 {112 const needed_alignment = @alignOf(Manifest.File) - (contents.items.len % @alignOf(Manifest.File));
114 const relative = try std.fs.path.relative(gpa, cwd, null, prefix, path);113 assert(needed_alignment >= 1); // Always need at least a null byte.
115 errdefer gpa.free(relative);114 // Extra +1 here is due to the requirement to keep at least 1 unused capacity in `Manifest.contents` at all times.
116 var component_iterator: std.fs.path.NativeComponentIterator = .init(relative);115 try contents.ensureUnusedCapacity(gpa, needed_alignment + 1);
117 if (component_iterator.root() != null) {116 contents.appendNTimesAssumeCapacity(0, needed_alignment);
118 return error.NotASubPath;117
119 }118 return 0;
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;
125}119}
126120
127/// This is 128 bits - Even with 2^54 cache entries, the probably of a collision would be under 10^-6121/// 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;...@@ -130,10 +124,6 @@ pub const hex_digest_len = bin_digest_len * 2;
130pub const BinDigest = [bin_digest_len]u8;124pub const BinDigest = [bin_digest_len]u8;
131pub const HexDigest = [hex_digest_len]u8;125pub 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
137/// The type used for hashing file contents. Currently, this is SipHash128(1, 3), because it127/// The type used for hashing file contents. Currently, this is SipHash128(1, 3), because it
138/// provides enough collision resistance for the Manifest use cases, while being one of our128/// provides enough collision resistance for the Manifest use cases, while being one of our
139/// fastest options right now.129/// fastest options right now.
...@@ -143,56 +133,12 @@ pub const Hasher = crypto.auth.siphash.SipHash128(1, 3);...@@ -143,56 +133,12 @@ pub const Hasher = crypto.auth.siphash.SipHash128(1, 3);
143/// Refresh this with new random bytes when the manifest133/// Refresh this with new random bytes when the manifest
144/// format is modified in a non-backwards-compatible way.134/// format is modified in a non-backwards-compatible way.
145pub const hasher_init: Hasher = Hasher.init(&.{135pub const hasher_init: Hasher = Hasher.init(&.{
146 0x33, 0x52, 0xa2, 0x84,136 0x02, 0xe9, 0xfa, 0xfe,
147 0xcf, 0x17, 0x56, 0x57,137 0xe0, 0x95, 0x81, 0x55,
148 0x01, 0xbb, 0xcd, 0xe4,138 0xf5, 0x5a, 0x15, 0xcb,
149 0x77, 0xd6, 0xf0, 0x60,139 0x4a, 0xf4, 0x00, 0x09,
150});140});
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
196pub const HashHelper = struct {142pub const HashHelper = struct {
197 hasher: Hasher = hasher_init,143 hasher: Hasher = hasher_init,
198144
...@@ -299,7 +245,7 @@ pub const HashHelper = struct {...@@ -299,7 +245,7 @@ pub const HashHelper = struct {
299245
300pub fn binToHex(bin_digest: BinDigest) HexDigest {246pub fn binToHex(bin_digest: BinDigest) HexDigest {
301 var out_digest: HexDigest = undefined;247 var out_digest: HexDigest = undefined;
302 var w: std.Io.Writer = .fixed(&out_digest);248 var w: Io.Writer = .fixed(&out_digest);
303 w.printHex(&bin_digest, .lower) catch unreachable;249 w.printHex(&bin_digest, .lower) catch unreachable;
304 return out_digest;250 return out_digest;
305}251}
...@@ -319,33 +265,337 @@ pub const Lock = struct {...@@ -319,33 +265,337 @@ pub const Lock = struct {
319 }265 }
320};266};
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.
322pub const Manifest = struct {271pub const Manifest = struct {
323 cache: *Cache,272 cache: *Cache,
324 /// Current state for incremental hashing.273 /// Current state for incremental hashing.
325 hash: HashHelper,274 hash: HashHelper,
275 /// When this is null, `Manifest` is in "pre-check" phase. Otherwise it is in "post-check" phase.
326 manifest_file: ?Io.File,276 manifest_file: ?Io.File,
327 manifest_dirty: bool,277 state: State,
328 /// Set this flag to true before calling hit() in order to indicate that278 /// Set this flag to true before calling `check` in order to indicate that upon a cache hit, the code
329 /// upon a cache hit, the code using the cache will not modify the files279 /// using the cache will not modify the files within the cache directory. This allows multiple processes
330 /// within the cache directory. This allows multiple processes to utilize280 /// to utilize the same cache directory at the same time.
331 /// the same cache directory at the same time.
332 want_shared_lock: bool = true,281 want_shared_lock: bool = true,
333 have_exclusive_lock: bool = false,282 have_exclusive_lock: bool = false,
334 // Indicate that we want isProblematicTimestamp to perform a filesystem write in283 // Indicate that we want isProblematicTimestamp to perform a filesystem write in
335 // order to obtain a problematic timestamp for the next call. Calls after that284 // order to obtain a problematic timestamp for the next call. Calls after that
336 // will then use the same timestamp, to avoid unnecessary filesystem writes.285 // will then use the same timestamp, to avoid unnecessary filesystem writes.
337 want_refresh_timestamp: bool = true,286 want_refresh_timestamp: bool = true,
338 files: Files = .{},287 /// Uses `Cache.gpa`.
339 hex_digest: HexDigest,288 files: Files = .empty,
340 diagnostic: Diagnostic = .none,289 /// Indexes line up with `files`, but only up until `hit` is called. Uses
290 /// `Cache.gpa`.
291 input_paths: std.ArrayList(InputPath) = .empty,
341 /// Keeps track of the last time we performed a file system write to observe292 /// Keeps track of the last time we performed a file system write to observe
342 /// what time the file system thinks it is, according to its own granularity.293 /// what time the file system thinks it is, according to its own granularity.
343 recent_problematic_timestamp: Io.Timestamp = .zero,294 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) {308 pub const Files = std.array_hash_map.Custom(File.Offset, void, File.HashContext, false);
346 none,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) {
347 manifest_create: Io.File.OpenError,595 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,
349 manifest_lock: Io.File.LockError,599 manifest_lock: Io.File.LockError,
350 file_open: FileOp,600 file_open: FileOp,
351 file_stat: FileOp,601 file_stat: FileOp,
...@@ -353,115 +603,274 @@ pub const Manifest = struct {...@@ -353,115 +603,274 @@ pub const Manifest = struct {
353 file_hash: FileOp,603 file_hash: FileOp,
354604
355 pub const FileOp = struct {605 pub const FileOp = struct {
356 file_index: usize,606 file_offset: File.Offset,
357 err: anyerror,607 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 }
358 };618 };
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 {638 pub fn fmt(diagnostic: CheckDiagnostic, manifest: *const Manifest) Format {
364 pub fn hash(fc: FilesContext, file: File) u32 {639 return .{
365 _ = fc;640 .diagnostic = diagnostic,
366 return file.prefixed_path.hash();641 .manifest = manifest,
642 };
367 }643 }
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 {651 pub fn init(other: Io.File.Stat) Stat {
370 _ = fc;652 return .{
371 _ = b_index;653 .size = other.size,
372 return a.prefixed_path.eql(b.prefixed_path);654 .inode = other.inode,
655 .mtime = other.mtime,
656 };
373 }657 }
374 };658 };
375659
376 const FilesAdapter = struct {660 pub const PathHandle = union(enum) {
377 pub fn eql(context: @This(), a: PrefixedPath, b: File, b_index: usize) bool {661 file: ?Io.File,
378 _ = context;662 /// If provided, this handle must be opened with iteration capability.
379 _ = b_index;663 dir: ?Io.Dir,
380 return a.eql(b.prefixed_path);664
665 pub fn isDirectory(this: @This()) bool {
666 return this == .dir;
381 }667 }
382668
383 pub fn hash(context: @This(), key: PrefixedPath) u32 {669 pub fn have(this: @This()) bool {
384 _ = context;670 return switch (this) {
385 return key.hash();671 .file => |opt_file| opt_file != null,
672 .dir => |opt_dir| opt_dir != null,
673 };
386 }674 }
387 };675 };
388676
389 /// Add a file as a dependency of process being cached. When `hit` is677 pub const AddInputPathOptions = struct {
390 /// called, the file's contents will be checked to ensure that it matches678 /// If provided, will be closed when `check` is called, unless `request_handle` is also set.
391 /// the contents from previous times.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.
392 ///693 ///
393 /// Max file size will be used to determine the amount of space the file contents694 /// The contents of the input file may be requested and subsequently
394 /// are allowed to take up in memory. If max_file_size is null, then the contents695 /// obtained via methods of the returned `InputPath.Index` after calling
395 /// will not be loaded into memory.696 /// `hit`.
396 ///697 ///
397 /// Returns the index of the entry in the `files` array list. You can use it698 /// Contents of a directory are considered to be the sorted list of file
398 /// to access the contents of the file after calling `hit()` like so: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.
399 ///702 ///
400 /// ```703 /// See also:
401 /// var file_contents = cache_hash.files.keys()[file_index].contents.?;704 /// * `addDiscoveredPath`
402 /// ```705 pub fn addInputPath(m: *Manifest, path: Path, options: AddInputPathOptions) Allocator.Error!InputPath.Index {
403 pub fn addFilePath(m: *Manifest, file_path: Path, max_file_size: ?usize) !usize {706 assert(m.state == .input);
404 return addOpenedFile(m, file_path, null, max_file_size);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));
405 }781 }
406782
407 /// Same as `addFilePath` except the file has already been opened.783 pub fn addInputPathOptional(m: *Manifest, opt_path: ?Path, options: AddInputPathOptions) Allocator.Error!void {
408 pub fn addOpenedFile(m: *Manifest, path: Path, handle: ?Io.File, max_file_size: ?usize) !usize {784 assert(m.state == .input);
409 const gpa = m.cache.gpa;785 m.hash.add(opt_path != null);
410 try m.files.ensureUnusedCapacity(gpa, 1);786 _ = try addInputPath(m, opt_path orelse return, options);
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);
418 }787 }
419788
420 fn addFileInner(self: *Manifest, prefixed_path: PrefixedPath, handle: ?Io.File, max_file_size: ?usize) usize {789 pub const AddInputDepFileError = error{
421 const gop = self.files.getOrPutAssumeCapacityAdapted(prefixed_path, FilesAdapter{});790 InvalidDepFile,
422 if (gop.found_existing) {791 } || Allocator.Error || Io.File.OpenError || Io.File.Reader.Error;
423 self.cache.gpa.free(prefixed_path.sub_path);792
424 gop.key_ptr.updateMaxSize(max_file_size);793 pub fn addInputDepFile(m: *Manifest, path: Path, diagnostic: ?*DepTokenizer.Token) AddInputDepFileError!void {
425 gop.key_ptr.updateHandle(handle);794 assert(m.manifest_file == null);
426 return gop.index;795 assert(m.state == .input);
427 }796
428 gop.key_ptr.* = .{797 const cache = m.cache;
429 .prefixed_path = prefixed_path,798 const gpa = cache.gpa;
430 .contents = null,799 const io = cache.io;
431 .max_file_size = max_file_size,800
432 .stat = undefined,801 // TODO: change DepTokenizer to be streaming rather than operating on slice of bytes.
433 .bin_digest = undefined,802 const prev_len = m.all_input_content.items.len;
434 .handle = handle,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.?,
435 };812 };
436813
437 self.hash.add(prefixed_path.prefix);814 var aux: std.ArrayList(u8) = .empty;
438 self.hash.addBytes(prefixed_path.sub_path);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 };
441 }833 }
442834
443 pub fn addOptionalFilePath(self: *Manifest, optional_file_path: ?Path) !void {835 pub const CheckResult = union(enum) {
444 self.hash.add(optional_file_path != null);836 hit,
445 const file_path = optional_file_path orelse return;837 incomplete_manifest,
446 _ = try self.addFilePath(file_path, null);838 invalid_manifest,
447 }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 {859 pub fn fmt(check_result: CheckResult, manifest: *const Manifest) Format {
450 assert(self.manifest_file == null);860 return .{
451 return self.addDepFileMaybePost(dir, dep_file_sub_path);861 .check_result = check_result,
452 }862 .manifest = manifest,
863 };
864 }
865 };
453866
454 pub const HitError = error{867 pub const CheckError = error{
455 /// Unable to check the cache for a reason that has been recorded into868 /// Unable to check the cache for a reason that has been recorded into
456 /// the `diagnostic` field.869 /// the `diagnostic` field.
457 CacheCheckFailed,870 CacheCheckFailed,
458 /// A cache manifest file exists however it could not be parsed.871 } || Allocator.Error || Io.Cancelable;
459 InvalidFormat,
460 OutOfMemory,
461 Canceled,
462 };
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.
465 /// A hex encoding of its hash is available by calling `final`.874 /// A hex encoding of its hash is available by calling `final`.
466 ///875 ///
467 /// This function will also acquire an exclusive lock to the manifest file. This means876 /// This function will also acquire an exclusive lock to the manifest file. This means
...@@ -473,52 +882,53 @@ pub const Manifest = struct {...@@ -473,52 +882,53 @@ pub const Manifest = struct {
473 /// The lock on the manifest file is released when `deinit` is called. As another882 /// The lock on the manifest file is released when `deinit` is called. As another
474 /// option, one may call `toOwnedLock` to obtain a smaller object which can represent883 /// option, one may call `toOwnedLock` to obtain a smaller object which can represent
475 /// the lock. `deinit` is safe to call whether or not `toOwnedLock` has been called.884 /// 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 {
477 const node = parent_progress_node.start("Reusing Cache Artifacts", 0);890 const node = parent_progress_node.start("Reusing Cache Artifacts", 0);
478 defer node.end();891 defer node.end();
479 return hitInner(man);892 return checkProgressless(man, diag);
480 }893 }
481894
482 pub fn hitInner(self: *Manifest) HitError!bool {895 pub fn checkProgressless(man: *Manifest, diag: *CheckDiagnostic) CheckError!CheckResult {
483 assert(self.manifest_file == null);896 assert(man.state == .input);
484897 assert(man.manifest_file == null);
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);
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);906 var input_digest: BinDigest = undefined;
496 manifest_file_path[hex_digest_len..][0..ext.len].* = ext.*;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
500 // We'll try to open the cache with an exclusive lock, but if that would block913 // We'll try to open the cache with an exclusive lock, but if that would block
501 // and `want_shared_lock` is set, a shared lock might be sufficient, so we'll914 // and `want_shared_lock` is set, a shared lock might be sufficient, so we'll
502 // open with a shared lock instead.915 // open with a shared lock instead.
503 while (true) {916 while (true) {
504 if (self.cache.manifest_dir.createFile(io, &manifest_file_path, .{917 if (man.cache.manifest_dir.createFile(io, manifest_file_path, .{
505 .read = true,918 .read = true,
506 .truncate = false,919 .truncate = false,
507 .lock = .exclusive,920 .lock = .exclusive,
508 .lock_nonblocking = self.want_shared_lock,921 .lock_nonblocking = man.want_shared_lock,
509 })) |manifest_file| {922 })) |manifest_file| {
510 self.manifest_file = manifest_file;923 man.manifest_file = manifest_file;
511 self.have_exclusive_lock = true;924 man.have_exclusive_lock = true;
512 break;925 break;
513 } else |err| switch (err) {926 } else |err| switch (err) {
514 error.WouldBlock => {927 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, .{
516 .mode = .read_write,929 .mode = .read_write,
517 .lock = .shared,930 .lock = .shared,
518 }) catch |e| {931 }) catch |e| return fail(diag, .{ .manifest_create = e });
519 self.diagnostic = .{ .manifest_create = e };
520 return error.CacheCheckFailed;
521 };
522 break;932 break;
523 },933 },
524 error.FileNotFound => {934 error.FileNotFound => {
...@@ -531,317 +941,699 @@ pub const Manifest = struct {...@@ -531,317 +941,699 @@ pub const Manifest = struct {
531 // disambiguates by returning EEXIST, indicating original941 // disambiguates by returning EEXIST, indicating original
532 // failure was a race, or ENOENT, indicating deletion of942 // failure was a race, or ENOENT, indicating deletion of
533 // the directory of our open handle.943 // the directory of our open handle.
534 if (!builtin.os.tag.isDarwin()) {944 if (!builtin.os.tag.isDarwin()) return fail(diag, .{ .manifest_create = error.FileNotFound });
535 self.diagnostic = .{ .manifest_create = error.FileNotFound };
536 return error.CacheCheckFailed;
537 }
538945
539 if (self.cache.manifest_dir.createFile(io, &manifest_file_path, .{946 if (man.cache.manifest_dir.createFile(io, manifest_file_path, .{
540 .read = true,947 .read = true,
541 .truncate = false,948 .truncate = false,
542 .lock = .exclusive,949 .lock = .exclusive,
543 .lock_nonblocking = self.want_shared_lock,950 .lock_nonblocking = man.want_shared_lock,
544 .exclusive = true,951 .exclusive = true,
545 })) |manifest_file| {952 })) |manifest_file| {
546 self.manifest_file = manifest_file;953 man.manifest_file = manifest_file;
547 self.have_exclusive_lock = true;954 man.have_exclusive_lock = true;
548 break;955 break;
549 } else |excl_err| switch (excl_err) {956 } else |excl_err| switch (excl_err) {
550 error.WouldBlock, error.PathAlreadyExists => continue,957 error.WouldBlock, error.PathAlreadyExists => continue,
551 error.FileNotFound => {958 error.FileNotFound => return fail(diag, .{ .manifest_create = error.FileNotFound }),
552 self.diagnostic = .{ .manifest_create = error.FileNotFound };
553 return error.CacheCheckFailed;
554 },
555 error.Canceled => |e| return e,959 error.Canceled => |e| return e,
556 else => |e| {960 else => |e| return fail(diag, .{ .manifest_create = e }),
557 self.diagnostic = .{ .manifest_create = e };
558 return error.CacheCheckFailed;
559 },
560 }961 }
561 },962 },
562 error.Canceled => |e| return e,963 error.Canceled => |e| return e,
563 else => |e| {964 else => |e| return fail(diag, .{ .manifest_create = e }),
564 self.diagnostic = .{ .manifest_create = e };965 }
565 return error.CacheCheckFailed;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 } }),
566 },1334 },
1335 .populated => unreachable,
567 }1336 }
568 }1337 }
1338 }
5691339
570 self.want_refresh_timestamp = true;1340 /// Upon return, disk_contents digest and stat will be populated regardless of whether hit or miss occurs.
5711341 fn checkInputPath(
572 const input_file_count = self.files.entries.len;1342 m: *Manifest,
5731343 diag: *CheckDiagnostic,
574 // We're going to construct a second hash. Its input will begin with the digest we've1344 file_off: File.Offset,
575 // already computed (`bin_digest`), and then it'll have the digests of each input file,1345 input_path: *InputPath,
576 // including "post" files (see `addFilePost`). If this is a hit, we learn the set of "post"1346 disk_contents: []u8,
577 // files from the manifest on disk. If this is a miss, we'll learn those from future calls1347 input_contents: []u8,
578 // to `addFilePost` etc. As such, the state of `self.hash.hasher` after this function1348 ) CheckError!CheckResult {
579 // depends on whether this is a hit or a miss.1349 const cache = m.cache;
580 //1350 const io = cache.io;
581 // If we return `true` indicating a cache hit, then `self.hash.hasher` must already include1351 const gpa = cache.gpa;
582 // the digests of the "post" files, so the caller can call `final`. Otherwise, on a cache1352 const disk_file = file_off.get(disk_contents);
583 // miss, `self.hash.hasher` will include the digests of all non-"post" files -- that is,1353 const input_file = file_off.get(input_contents);
584 // the ones we've already been told about. The rest will be discovered through calls to1354 const parent_dir = cache.prefixes()[disk_file.flags.prefix].handle;
585 // `addFilePost` etc, which will update the hasher. After all files are added, the user can1355 const file_path = file_off.path(disk_contents);
586 // use `final`, and will at some point `writeManifest` the file list to disk.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;1364 if (input_path.have_stat) {
589 self.hash.hasher.update(&bin_digest);1365 const changed = try disk_file.setStatChanged(m, input_file.stat());
1366 if (!changed) return .hit;
1367 }
5901368
591 hit: {1369 if (disk_file.flags.metadata_only) {
592 const file_digests_populated: usize = digests: {1370 if (!input_path.have_stat) {
593 switch (try self.hitWithCurrentLock()) {1371 // Since this is an input file, FileNotFound counts as a failure, not a cache miss.
594 .hit => break :hit,1372 const actual_stat = switch (input_path.getHandle(input_file)) {
595 .miss => |m| if (!try self.upgradeToExclusiveLock()) {1373 .none => parent_dir.statFile(io, file_path, .{}) catch |err| switch (err) {
596 break :digests m.file_digests_populated;1374 error.Canceled => |e| return e,
1375 else => |e| return fail(diag, .{ .file_stat = .{
1376 .file_offset = file_off,
1377 .err = e,
1378 } }),
597 },1379 },
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 } });
598 }1404 }
599 // We've just had a miss with the shared lock, and upgraded to an exclusive lock. Someone1405 const stat: Stat = .init(actual_stat);
600 // else might have modified the digest, so we need to check again before deciding to miss.1406 const changed = try disk_file.setStatChanged(m, stat);
601 // Before trying again, we must reset `self.hash.hasher` and `self.files`.1407 disk_file.hashFromMetadata();
602 // This is basically just the first half of `unhit`.1408 if (!changed) return .hit;
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 }
639 }1409 }
640 return false;1410 return .{ .metadata_changed = file_off };
641 }1411 }
6421412
643 if (self.want_shared_lock) {1413 if (input_path.contents.get(m.all_input_content.items)) |file_or_dir_contents| {
644 self.downgradeToSharedLock() catch |err| {1414 var hasher = hasher_init;
645 self.diagnostic = .{ .manifest_lock = err };1415 hasher.update(file_or_dir_contents);
646 return error.CacheCheckFailed;1416 hasher.final(&disk_file.digest);
647 };
648 }
6491417
650 return true;1418 if (mem.eql(u8, &disk_file.digest, &input_file.digest)) return .hit;
651 }1419 return .{ .contents_changed = file_off };
1420 }
6521421
653 /// Assumes that `self.hash.hasher` has been updated only with the original digest and that1422 if (disk_file.flags.is_directory) {
654 /// `self.files` contains only the original input files.1423 const opened_dir = if (input_path.have_handle)
655 fn hitWithCurrentLock(self: *Manifest) HitError!union(enum) {1424 input_path.handle.dir
656 hit,1425 else
657 miss: struct {1426 parent_dir.openDir(io, file_path, .{
658 file_digests_populated: usize,1427 .iterate = true,
659 },1428 .access_sub_paths = false,
660 } {1429 }) catch |err| switch (err) {
661 const gpa = self.cache.gpa;1430 error.Canceled => |e| return e,
662 const io = self.cache.io;1431 else => |e| return fail(diag, .{ .file_open = .{
663 const input_file_count = self.files.entries.len;1432 .file_offset = file_off,
664 var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded1433 .err = e,
665 var manifest_reader = self.manifest_file.?.reader(io, &tiny_buffer); // Reads positionally from zero.1434 } }),
666 const limit: std.Io.Limit = .limited(manifest_file_size_max);1435 };
667 const file_contents = manifest_reader.interface.allocRemaining(gpa, limit) catch |err| switch (err) {1436 defer if (!input_path.request_handle) opened_dir.close(io) else {
668 error.OutOfMemory => |e| return e,1437 input_path.handle = .{ .dir = opened_dir };
669 error.StreamTooLong => return error.OutOfMemory,1438 input_path.have_handle = true;
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;
706 };1439 };
7071440
708 const prefix = fmt.parseInt(u8, prefix_str, 10) catch return error.InvalidFormat;1441 if (!input_path.have_stat) {
709 if (prefix >= self.cache.prefixes_len) return error.InvalidFormat;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: {1469 if (mem.eql(u8, &disk_file.digest, &input_file.digest)) return .hit;
714 const prefixed_path: PrefixedPath = .{1470 return .{ .contents_changed = file_off };
715 .prefix = prefix,1471 } else {
716 .sub_path = file_path, // expires with file_contents1472 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 } }),
717 };1481 };
718 if (idx < input_file_count) {1482 defer if (!input_path.request_handle) opened_file.close(io) else {
719 const file = &self.files.keys()[idx];1483 input_path.handle = .{ .file = opened_file };
720 if (!file.prefixed_path.eql(prefixed_path))1484 input_path.have_handle = true;
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;
751 };1485 };
7521486
753 const pp = cache_hash_file.prefixed_path;1487 if (!input_path.have_stat) {
754 const dir = self.cache.prefixes()[pp.prefix].handle;1488 const actual_stat = opened_file.stat(io) catch |err| switch (err) {
755 const this_file = dir.openFile(io, pp.sub_path, .{ .mode = .read_only }) catch |err| switch (err) {1489 error.Canceled => |e| return e,
756 error.FileNotFound => {1490 else => |e| return fail(diag, .{ .file_stat = .{
757 // Every digest before this one has been populated successfully.1491 .file_offset = file_off,
758 return .{ .miss = .{ .file_digests_populated = idx } };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 });
759 },1512 },
760 error.Canceled => |e| return e,1513 .unrequested => hashFile(io, opened_file, &disk_file.digest) catch |err| switch (err) {
761 else => |e| {1514 error.Canceled => |e| return e,
762 self.diagnostic = .{ .file_open = .{1515 else => |e| return fail(diag, .{ .file_read = .{
763 .file_index = idx,1516 .file_offset = file_off,
764 .err = e,1517 .err = e,
765 } };1518 } }),
766 return error.CacheCheckFailed;
767 },1519 },
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 } }),
768 };1549 };
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)) {1551 const actual_is_directory = actual_stat.kind == .directory;
790 // The actual file has an unreliable timestamp, force it to be hashed1552 if (actual_is_directory != file.flags.is_directory) return .{ .directory_status_changed = file_off };
791 cache_hash_file.stat.mtime = .zero;
792 cache_hash_file.stat.inode = 0;
793 }
7941553
795 var actual_digest: BinDigest = undefined;1554 const changed = try file.setStatChanged(m, .init(actual_stat));
796 hashFile(io, this_file, &actual_digest) catch |err| {1555 file.hashFromMetadata();
797 self.diagnostic = .{ .file_read = .{1556 if (changed) return .{ .metadata_changed = file_off };
798 .file_index = idx,1557 return .hit;
799 .err = err,1558 }
800 } };
801 return error.CacheCheckFailed;
802 };
8031559
804 if (!mem.eql(u8, &cache_hash_file.bin_digest, &actual_digest)) {1560 if (file.flags.is_directory) {
805 cache_hash_file.bin_digest = actual_digest;1561 const opened_dir = parent_dir.openDir(io, file_path, .{
806 // keep going until we have the input file digests1562 .iterate = true,
807 any_file_changed = true;1563 .access_sub_paths = false,
808 }1564 }) catch |err| switch (err) {
809 }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) {1594 if (!mem.eql(u8, &file.digest, &prev_digest)) return .{ .contents_changed = file_off };
812 self.hash.hasher.update(&cache_hash_file.bin_digest);
813 }1595 }
1596 return .hit;
814 }1597 }
8151598
816 // If the manifest was somehow missing one of our input files, or if any file hash has changed,1599 const opened_file = parent_dir.openFile(io, file_path, .{ .mode = .read_only }) catch |err| switch (err) {
817 // then this is a cache miss. However, we have successfully populated some or all of the file1600 error.FileNotFound => return .{ .path_deleted = file_off },
818 // digests.1601 error.IsDir => return .{ .directory_status_changed = file_off },
819 if (any_file_changed or idx < input_file_count) {1602 error.Canceled => |e| return e,
820 return .{ .miss = .{ .file_digests_populated = idx } };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 };
821 }1629 }
8221630
823 return .hit;1631 return .hit;
824 }1632 }
8251633
826 /// Reset `self.hash.hasher` to the state it should be in after `hit` returns `false`.1634 fn fail(ptr: *CheckDiagnostic, d: CheckDiagnostic) error{CacheCheckFailed} {
827 /// The hasher contains the original input digest, and all original input file digests (i.e.1635 ptr.* = d;
828 /// not including post files).1636 return error.CacheCheckFailed;
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 }
845 }1637 }
8461638
847 fn isProblematicTimestamp(man: *Manifest, timestamp: Io.Timestamp) error{Canceled}!bool {1639 fn isProblematicTimestamp(man: *Manifest, timestamp: Io.Timestamp) error{Canceled}!bool {
...@@ -887,303 +1679,354 @@ pub const Manifest = struct {...@@ -887,303 +1679,354 @@ pub const Manifest = struct {
887 return timestamp.nanoseconds >= man.recent_problematic_timestamp.nanoseconds;1679 return timestamp.nanoseconds >= man.recent_problematic_timestamp.nanoseconds;
888 }1680 }
8891681
890 fn populateFileHash(self: *Manifest, ch_file: *File) !void {1682 pub const AddDiscoveredPathOptions = struct {
891 const io = self.cache.io;1683 discovered_path: DiscoveredPath,
8921684 handle: PathHandle = .{ .file = null },
893 if (ch_file.handle) |handle| {1685 stat: ?Stat = null,
894 return populateFileHashHandle(self, ch_file, handle);1686 /// If it is a directory, there is a special encoding required for contents, which
895 } else {1687 /// is null-separated sorted entries, each one prefixed with `File.Kind`.
896 const pp = ch_file.prefixed_path;1688 contents: ?[]const u8 = null,
897 const dir = self.cache.prefixes()[pp.prefix].handle;1689 metadata_only: bool = false,
898 const handle = try dir.openFile(io, pp.sub_path, .{});1690 /// Populated if and only if `error.FileSystemFailure` is returned from `addDiscoveredPath`.
899 defer handle.close(io);1691 diagnostic: ?*AddDiscoveredPathDiagnostic = null,
900 return populateFileHashHandle(self, ch_file, handle);1692 };
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;
9071693
908 const actual_stat = try io_file.stat(io);1694 pub const DiscoveredPath = union(enum) {
909 ch_file.stat = .{1695 unresolved: Path,
910 .size = actual_stat.size,1696 prefixed: PrefixedPath,
911 .mtime = actual_stat.mtime,1697 };
912 .inode = actual_stat.inode,
913 };
9141698
915 if (try self.isProblematicTimestamp(ch_file.stat.mtime)) {1699 pub const AddDiscoveredPathDiagnostic = union(enum) {
916 // The actual file has an unreliable timestamp, force it to be hashed1700 none,
917 ch_file.stat.mtime = .zero;1701 open_file: Io.File.OpenError,
918 ch_file.stat.inode = 0;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 }
919 }1713 }
1714 };
9201715
921 if (ch_file.max_file_size) |max_file_size| {1716 pub const AddDiscoveredPathError = error{
922 if (ch_file.stat.size > max_file_size) return error.FileTooBig;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 cpu1722 /// Add a file or directory as a dependency of process being cached, after cache miss occurs.
925 // cache while doing hashing.1723 ///
926 const contents = try gpa.alloc(u8, @intCast(ch_file.stat.size));1724 /// See also:
927 errdefer gpa.free(contents);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;1753 const gop = m.files.getOrPutAssumeCapacityContext(new_file_offset, .{ .contents = m.contents.items });
930 var off: usize = 0;1754 m.files.lockPointers();
931 while (true) {1755 defer m.files.unlockPointers();
932 const bytes_read = try io_file.readPositional(io, &.{contents[off..]}, off);1756
933 if (bytes_read == 0) break;1757 const file_offset = if (gop.found_existing) h: {
934 hasher.update(contents[off..][0..bytes_read]);1758 m.contents.shrinkRetainingCapacity(@backingInt(new_file_offset));
935 off += bytes_read;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);
936 }1780 }
937 hasher.final(&ch_file.bin_digest);1781 return;
938
939 ch_file.contents = contents;
940 } else {
941 try hashFile(io, io_file, &ch_file.bin_digest);
942 }1782 }
9431783
944 self.hash.hasher.update(&ch_file.bin_digest);1784 const need_stat = options.stat == null;
945 }1785 const metadata_only = header.flags.metadata_only;
9461786 const prefix = header.flags.prefix;
947 /// Add a file as a dependency of process being cached, after the initial hash has been1787
948 /// calculated. This is useful for processes that don't know all the files that1788 switch (options.handle) {
949 /// are depended on ahead of time. For example, a source file that can import other files1789 .dir => |opt_handle| if (opt_handle) |handle| {
950 /// will need to be recompiled if the imported file is changed.1790 try populateDirectory(m, header, need_stat, handle, options.contents, metadata_only, options.diagnostic);
951 pub fn addFilePostFetch(self: *Manifest, file_path: []const u8, max_file_size: usize) ![]const u8 {1791 } else {
952 assert(self.manifest_file != null);1792 const dir = cache.prefixes()[prefix].handle;
9531793 const sub_path = file_offset.path(m.contents.items);
954 const gpa = self.cache.gpa;1794 const handle = dir.openDir(io, sub_path, .{
955 const prefixed_path = try self.cache.findPrefix(file_path);1795 .access_sub_paths = false,
956 errdefer gpa.free(prefixed_path.sub_path);1796 .iterate = true,
9571797 }) catch |err| switch (err) {
958 const gop = try self.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{});1798 error.Canceled => |e| return e,
959 errdefer _ = self.files.pop();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) {1808 .file => |opt_handle| if (opt_handle) |handle| {
962 gpa.free(prefixed_path.sub_path);1809 try populateFile(m, header, need_stat, handle, options.contents, metadata_only, options.diagnostic);
963 return gop.key_ptr.contents.?;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 },
964 }1823 }
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.?;
980 }1824 }
9811825
982 /// Add a file as a dependency of process being cached, after the initial hash has been1826 pub const AddDiscoveredDepFileError = error{
983 /// calculated.1827 InvalidDepFile,
984 ///1828 } || AddDiscoveredPathError || Io.File.OpenError || Io.File.Reader.Error;
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;
10111829
1012 const gop = try man.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{});1830 pub const AddDiscoveredDepFileDiagnostic = union(enum) {
1013 errdefer _ = man.files.pop();1831 add_discovered_path: AddDiscoveredPathDiagnostic,
10141832 dep_tokenizer: DepTokenizer.Token,
1015 if (gop.found_existing) return false;1833 };
10161834
1017 gop.key_ptr.* = .{1835 /// Add a GNU make style dep file as a dependency of process being cached, after cache miss occurs.
1018 .prefixed_path = prefixed_path,1836 ///
1019 .max_file_size = null,1837 /// See also:
1020 .handle = null,1838 /// * `addDiscoveredPath
1021 .stat = undefined,1839 /// * `addDiscoveredManifest`
1022 .bin_digest = undefined,1840 pub fn addDiscoveredDepFile(
1023 .contents = null,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.?,
1024 };1863 };
10251864
1026 man.files.lockPointers();1865 var aux: std.ArrayList(u8) = .empty;
1027 defer man.files.unlockPointers();1866 defer aux.deinit(gpa);
10281867
1029 try man.populateFileHash(gop.key_ptr);1868 var it: DepTokenizer = .{ .bytes = m.all_input_content.items[prev_len..] };
1030 return true;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 };
1031 }1882 }
10321883
1033 /// Like `addFilePost` but when the file contents have already been loaded from disk.1884 fn populateFile(
1034 pub fn addFilePostContents(1885 m: *Manifest,
1035 man: *Manifest,1886 file: *File,
1036 file_path: []const u8,1887 need_stat: bool,
1037 bytes: []const u8,1888 handle: Io.File,
1038 stat: File.Stat,1889 contents: ?[]const u8,
1039 ) !void {1890 metadata_only: bool,
1040 assert(man.manifest_file != null);1891 diagnostic: ?*AddDiscoveredPathDiagnostic,
1041 const gpa = man.cache.gpa;1892 ) AddDiscoveredPathError!void {
1042 const prefixed_path = try man.cache.findPrefix(file_path);1893 const io = m.cache.io;
1043 var keep = false;1894
1044 defer if (!keep) gpa.free(prefixed_path.sub_path);1895 if (need_stat) {
1045 keep = try addPrefixedPathPostContents(man, prefixed_path, bytes, 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 }
1046 }1922 }
10471923
1048 /// Low level function. `prefixed_path` references cloned memory. Returns1924 fn populateDirectory(
1049 /// whether or not `prefixed_path.sub_path` should be kept.1925 m: *Manifest,
1050 pub fn addPrefixedPathPostContents(1926 file: *File,
1051 man: *Manifest,1927 need_stat: bool,
1052 prefixed_path: PrefixedPath,1928 handle: Io.Dir,
1053 bytes: []const u8,1929 contents: ?[]const u8,
1054 stat: File.Stat,1930 metadata_only: bool,
1055 ) !bool {1931 diagnostic: ?*AddDiscoveredPathDiagnostic,
1056 const gpa = man.cache.gpa;1932 ) AddDiscoveredPathError!void {
1057 const gop = try man.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{});1933 const cache = m.cache;
1058 errdefer _ = man.files.pop();1934 const io = cache.io;
10591935 const gpa = cache.gpa;
1060 if (gop.found_existing) return false;1936
10611937 if (need_stat) {
1062 const new_file = gop.key_ptr;1938 const stat = handle.stat(io) catch |err| switch (err) {
10631939 error.Canceled => |e| return e,
1064 new_file.* = .{1940 else => |e| {
1065 .prefixed_path = prefixed_path,1941 if (diagnostic) |d| d.* = .{ .stat_dir = e };
1066 .max_file_size = null,1942 return error.FileSystemFailure;
1067 .handle = null,1943 },
1068 .stat = stat,1944 };
1069 .bin_digest = undefined,1945 try file.setStat(m, .init(stat));
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;
1077 }1946 }
10781947 if (metadata_only) {
1079 {1948 file.hashFromMetadata();
1949 return;
1950 }
1951 if (contents) |bytes| {
1080 var hasher = hasher_init;1952 var hasher = hasher_init;
1081 hasher.update(bytes);1953 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 };
1083 }1965 }
1966 }
10841967
1085 man.hash.hasher.update(&new_file.bin_digest);1968 /// See also `hitDigestHex`.
1086 return true;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);
1087 }1974 }
10881975
1089 pub fn addDepFilePost(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void {1976 pub fn hitDigestHex(m: *Manifest) HexDigest {
1090 assert(self.manifest_file != null);1977 return binToHex(m.hitDigest());
1091 return self.addDepFileMaybePost(dir, dep_file_sub_path);
1092 }1978 }
10931979
1094 fn addDepFileMaybePost(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void {1980 /// See also `missDigestHex`.
1095 const gpa = self.cache.gpa;1981 pub fn missDigest(m: *Manifest) BinDigest {
1096 const io = self.cache.io;1982 assert(m.manifest_file != null);
1097 const dep_file_contents = try dir.readFileAlloc(io, dep_file_sub_path, gpa, .limited(manifest_file_size_max));1983 transitionToMissDiscovered(m);
1098 defer gpa.free(dep_file_contents);1984 m.state = .miss_digested;
10991985 return finalDigest(m);
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 }
1129 }1986 }
11301987
1131 /// Returns a binary hash of the inputs.1988 pub fn missDigestHex(m: *Manifest) HexDigest {
1132 pub fn finalBin(self: *Manifest) BinDigest {1989 return binToHex(m.missDigest());
1133 assert(self.manifest_file != null);1990 }
11341991
1135 // We don't close the manifest file yet, because we want to1992 fn finalDigest(m: *Manifest) BinDigest {
1136 // keep it locked until the API user is done using it.1993 const contents = m.contents.items;
1137 // We also don't write out the manifest yet, because until1994 const hasher = &m.hash.hasher;
1138 // cache_release is called we still might be working on creating
1139 // the artifacts to cache.
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.
1141 var bin_digest: BinDigest = undefined;2003 var bin_digest: BinDigest = undefined;
1142 self.hash.hasher.final(&bin_digest);2004 hasher.final(&bin_digest);
1143 return bin_digest;2005 return bin_digest;
1144 }2006 }
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
1152 /// If `want_shared_lock` is true, this function automatically downgrades the2008 /// If `want_shared_lock` is true, this function automatically downgrades the
1153 /// lock from exclusive to shared.2009 /// lock from exclusive to shared.
1154 pub fn writeManifest(self: *Manifest) !void {2010 pub fn finalize(m: *Manifest) !void {
1155 assert(self.have_exclusive_lock);2011 const io = m.cache.io;
1156 const io = self.cache.io;2012 const manifest_file = m.manifest_file.?;
1157 const manifest_file = self.manifest_file.?;2013
1158 if (self.manifest_dirty) {2014 assert(m.state == .miss_digested);
1159 self.manifest_dirty = false;2015 assert(m.have_exclusive_lock);
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 }
11682016
1169 if (self.want_shared_lock) {2017 {
1170 try self.downgradeToSharedLock();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;
1171 }2025 }
1172 }
11732026
1174 fn writeDirtyManifestToStream(self: *Manifest, fw: *Io.File.Writer) !void {2027 if (m.want_shared_lock) {
1175 try fw.interface.writeAll(manifest_header ++ "\n");2028 try m.downgradeToSharedLock();
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 });
1185 }2029 }
1186 try fw.end();
1187 }2030 }
11882031
1189 fn downgradeToSharedLock(self: *Manifest) !void {2032 fn downgradeToSharedLock(self: *Manifest) !void {
...@@ -1198,53 +2041,74 @@ pub const Manifest = struct {...@@ -1198,53 +2041,74 @@ pub const Manifest = struct {
1198 self.have_exclusive_lock = false;2041 self.have_exclusive_lock = false;
1199 }2042 }
12002043
1201 fn upgradeToExclusiveLock(self: *Manifest) error{CacheCheckFailed}!bool {2044 fn upgradeToExclusiveLock(m: *Manifest, diag: *CheckDiagnostic) error{CacheCheckFailed}!bool {
1202 if (self.have_exclusive_lock) return false;2045 if (m.have_exclusive_lock) return false;
1203 assert(self.manifest_file != null);2046 assert(m.manifest_file != null);
1204 const io = self.cache.io;2047 const io = m.cache.io;
12052048
1206 if (std.process.can_spawn or !builtin.single_threaded) {2049 if (std.process.can_spawn or !builtin.single_threaded) {
1207 const manifest_file = self.manifest_file.?;2050 const manifest_file = m.manifest_file.?;
1208 // Here we intentionally have a period where the lock is released, in case there are2051 // Here we intentionally have a period where the lock is released, in case there are
1209 // other processes holding a shared lock.2052 // other processes holding a shared lock.
1210 manifest_file.unlock(io);2053 manifest_file.unlock(io);
1211 manifest_file.lock(io, .exclusive) catch |err| {2054 manifest_file.lock(io, .exclusive) catch |err| return fail(diag, .{ .manifest_lock = err });
1212 self.diagnostic = .{ .manifest_lock = err };
1213 return error.CacheCheckFailed;
1214 };
1215 }2055 }
1216 self.have_exclusive_lock = true;2056 m.have_exclusive_lock = true;
1217 return true;2057 return true;
1218 }2058 }
12192059
1220 /// Obtain only the data needed to maintain a lock on the manifest file.2060 /// Obtain only the data needed to maintain a lock on the manifest file.
1221 /// The `Manifest` remains safe to deinit.2061 /// The `Manifest` remains safe to deinit.
1222 ///2062 ///
1223 /// Don't forget to call `writeManifest` before this!2063 /// Don't forget to call `finalize` before this!
1224 pub fn toOwnedLock(self: *Manifest) Lock {2064 pub fn toOwnedLock(self: *Manifest) Lock {
1225 defer self.manifest_file = null;2065 defer self.manifest_file = null;
1226 return .{ .manifest_file = self.manifest_file.? };2066 return .{ .manifest_file = self.manifest_file.? };
1227 }2067 }
12282068
1229 pub fn takeFiles(man: *Manifest) Files {2069 pub const SelfContainedFiles = struct {
1230 defer man.files = .empty;2070 /// References memory inside `contents`.
1231 return man.files;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);
1232 }2094 }
12332095
1234 pub fn freeFiles(gpa: Allocator, files: *Files) void {2096 pub fn borrowFiles(m: *const Manifest) SelfContainedFiles {
1235 for (files.keys()) |*file| file.deinit(gpa);2097 return .{
1236 files.deinit(gpa);2098 .files = m.files,
2099 .contents = m.contents,
2100 };
1237 }2101 }
12382102
1239 /// Releases the manifest file and frees any memory the Manifest was using.2103 /// Releases the manifest file and frees any memory the Manifest was using.
1240 /// `Manifest.hit` must be called first.2104 /// `Manifest.hit` must be called first.
1241 ///2105 ///
1242 /// Don't forget to call `writeManifest` before this!2106 /// Don't forget to call `finalize` before this!
1243 pub fn deinit(man: *Manifest) void {2107 pub fn deinit(m: *Manifest) void {
1244 const io = man.cache.io;2108 const io = m.cache.io;
1245 const gpa = man.cache.gpa;2109 const gpa = m.cache.gpa;
12462110
1247 if (man.manifest_file) |file| {2111 if (m.manifest_file) |file| {
1248 if (builtin.os.tag == .windows) {2112 if (builtin.os.tag == .windows) {
1249 // See Lock.release for why this is required on Windows2113 // See Lock.release for why this is required on Windows
1250 file.unlock(io);2114 file.unlock(io);
...@@ -1252,20 +2116,25 @@ pub const Manifest = struct {...@@ -1252,20 +2116,25 @@ pub const Manifest = struct {
12522116
1253 file.close(io);2117 file.close(io);
1254 }2118 }
1255 freeFiles(gpa, &man.files);2119 m.files.deinit(gpa);
1256 man.* = undefined;2120 m.contents.deinit(gpa);
2121 m.input_paths.deinit(gpa);
2122 m.all_input_content.deinit(gpa);
2123 m.* = undefined;
1257 }2124 }
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 {
1260 assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == man.cache.prefixes_len);2127 assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == man.cache.prefixes_len);
1261 buf.clearRetainingCapacity();2128 buf.clearRetainingCapacity();
1262 const gpa = man.cache.gpa;2129 const gpa = man.cache.gpa;
1263 const files = man.files.keys();2130 const files = man.files.keys();
1264 if (files.len > 0) {2131 if (files.len > 0) {
2132 const contents = man.contents.items;
1265 for (files) |file| {2133 for (files) |file| {
1266 try buf.ensureUnusedCapacity(gpa, file.prefixed_path.sub_path.len + 2);2134 const file_path = file.path(contents);
1267 buf.appendAssumeCapacity(file.prefixed_path.prefix + 1);2135 try buf.ensureUnusedCapacity(gpa, file_path.len + 2);
1268 buf.appendSliceAssumeCapacity(file.prefixed_path.sub_path);2136 buf.appendAssumeCapacity(@as(u8, file.get(contents).flags.prefix) + 1);
2137 buf.appendSliceAssumeCapacity(file_path);
1269 buf.appendAssumeCapacity(0);2138 buf.appendAssumeCapacity(0);
1270 }2139 }
1271 // The null byte is a separator, not a terminator.2140 // The null byte is a separator, not a terminator.
...@@ -1273,53 +2142,157 @@ pub const Manifest = struct {...@@ -1273,53 +2142,157 @@ pub const Manifest = struct {
1273 }2142 }
1274 }2143 }
12752144
1276 pub fn populateOtherManifest(man: *Manifest, other: *Manifest, prefix_map: [5]u8) Allocator.Error!void {2145 /// Add the full set of paths from another `Manifest` as a dependency of process being cached, after cache miss
1277 const gpa = other.cache.gpa;2146 /// occurs.
1278 assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == man.cache.prefixes_len);2147 ///
1279 assert(man.cache.prefixes_len == 5);2148 /// See also:
1280 for (man.files.keys()) |file| {2149 /// * `addDiscoveredPath
1281 const prefixed_path: PrefixedPath = .{2150 /// * `addDiscoveredDepFile`
1282 .prefix = prefix_map[file.prefixed_path.prefix],2151 pub fn addDiscoveredManifest(m: *Manifest, discovered: *const Manifest, prefix_map: [5]u8) Allocator.Error!void {
1283 .sub_path = try gpa.dupe(u8, file.prefixed_path.sub_path),2152 const gpa = m.cache.gpa;
1284 };2153 assert(m.manifest_file != null);
1285 errdefer gpa.free(prefixed_path.sub_path);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{});2175 const copy_bytes = discovered.contents.items[@backingInt(off)..next_off];
1288 errdefer _ = other.files.pop();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
1290 if (gop.found_existing) {2183 if (gop.found_existing) {
1291 gpa.free(prefixed_path.sub_path);2184 m.contents.shrinkRetainingCapacity(@backingInt(prev_contents_len));
1292 continue;2185 continue;
1293 }2186 }
12942187
1295 gop.key_ptr.* = .{2188 // Flags are already copied but the prefix is supposed to be filtered by `prefix_map`.
1296 .prefixed_path = prefixed_path,2189 const other_file = prev_contents_len.get(m.contents.items);
1297 .max_file_size = file.max_file_size,2190 other_file.flags.prefix = @intCast(prefix_map[other_file.flags.prefix]);
1298 .handle = file.handle,2191 }
1299 .stat = file.stat,2192 }
1300 .bin_digest = file.bin_digest,
1301 .contents = null,
1302 };
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;
1305 }2203 }
2204 hasher.final(bin_digest);
1306 }2205 }
1307};
13082206
1309fn hashFile(io: Io, file: Io.File, bin_digest: *[Hasher.mac_length]u8) Io.File.ReadPositionalError!void {2207 fn hashFileAppend(
1310 var buffer: [2048]u8 = undefined;2208 io: Io,
1311 var hasher = hasher_init;2209 file: Io.File,
1312 var offset: u64 = 0;2210 bin_digest: *[Hasher.mac_length]u8,
1313 while (true) {2211 al: *std.ArrayList(u8),
1314 const n = try file.readPositional(io, &.{&buffer}, offset);2212 gpa: Allocator,
1315 if (n == 0) break;2213 ) (Io.File.ReadPositionalError || Allocator.Error)!void {
1316 hasher.update(buffer[0..n]);2214 var hasher = hasher_init;
1317 offset += n;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);
1318 }2226 }
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.
1323fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp {2296fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp {
1324 const test_out_file = "test-filetimestamp.tmp";2297 const test_out_file = "test-filetimestamp.tmp";
13252298
...@@ -1348,11 +2321,16 @@ test "cache file and then recall it" {...@@ -1348,11 +2321,16 @@ test "cache file and then recall it" {
1348 const temp_manifest_dir = "temp_manifest_dir";2321 const temp_manifest_dir = "temp_manifest_dir";
13492322
1350 try tmp.dir.writeFile(io, .{ .sub_path = temp_file, .data = "Hello, world!\n" });2323 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
1352 // Wait for file timestamps to tick2330 // Wait for file timestamps to tick
1353 const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir);2331 const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir);
1354 while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) {2332 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);
1356 }2334 }
13572335
1358 var digest1: HexDigest = undefined;2336 var digest1: HexDigest = undefined;
...@@ -1365,45 +2343,53 @@ test "cache file and then recall it" {...@@ -1365,45 +2343,53 @@ test "cache file and then recall it" {
1365 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),2343 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
1366 .cwd = cwd,2344 .cwd = cwd,
1367 };2345 };
1368 cache.addPrefix(.{ .path = null, .handle = tmp.dir });2346 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
2347 cache.addPrefix(tmp_directory);
1369 defer cache.manifest_dir.close(io);2348 defer cache.manifest_dir.close(io);
13702349
1371 {2350 {
1372 var ch = cache.obtain();2351 var man = cache.obtain();
1373 defer ch.deinit();2352 defer man.deinit();
13742353
1375 ch.hash.add(true);2354 man.hash.add(true);
1376 ch.hash.add(@as(u16, 1234));2355 man.hash.add(@as(u16, 1234));
1377 ch.hash.addBytes("1234");2356 man.hash.addBytes("1234");
1378 _ = try ch.addFilePath(.initCwd(temp_file), null);2357 _ = try man.addInputPath(.{
13792358 .root_dir = tmp_directory,
1380 // There should be nothing in the cache2359 .sub_path = temp_file,
1381 try testing.expectEqual(false, try ch.hit(.none));2360 }, .{});
13822361
1383 digest1 = ch.final();2362 var diag: Manifest.CheckDiagnostic = undefined;
1384 try ch.writeManifest();2363 try testing.expectEqual(.incomplete_manifest, try man.check(&diag, .none));
2364
2365 digest1 = man.missDigestHex();
2366 try man.finalize();
1385 }2367 }
1386 {2368 {
1387 var ch = cache.obtain();2369 var man = cache.obtain();
1388 defer ch.deinit();2370 defer man.deinit();
13892371
1390 ch.hash.add(true);2372 man.hash.add(true);
1391 ch.hash.add(@as(u16, 1234));2373 man.hash.add(@as(u16, 1234));
1392 ch.hash.addBytes("1234");2374 man.hash.addBytes("1234");
1393 _ = try ch.addFilePath(.initCwd(temp_file), null);2375 _ = try man.addInputPath(.{
2376 .root_dir = tmp_directory,
2377 .sub_path = temp_file,
2378 }, .{});
13942379
1395 // Cache hit! We just "built" the same file2380 // Cache hit! We just "built" the same file
1396 try testing.expect(try ch.hit(.none));2381 var diag: Manifest.CheckDiagnostic = undefined;
1397 digest2 = ch.final();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);
1400 }2386 }
14012387
1402 try testing.expectEqual(digest1, digest2);2388 try testing.expectEqual(digest1, digest2);
1403 }2389 }
1404}2390}
14052391
1406test "check that changing a file makes cache fail" {2392test "check that changing a file causes cache miss" {
1407 const io = testing.io;2393 const io = testing.io;
14082394
1409 var tmp = testing.tmpDir(.{});2395 var tmp = testing.tmpDir(.{});
...@@ -1418,11 +2404,16 @@ test "check that changing a file makes cache fail" {...@@ -1418,11 +2404,16 @@ test "check that changing a file makes cache fail" {
1418 const updated_temp_file_contents = "Hello, world; but updated!\n";2404 const updated_temp_file_contents = "Hello, world; but updated!\n";
14192405
1420 try tmp.dir.writeFile(io, .{ .sub_path = temp_file, .data = original_temp_file_contents });2406 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
1422 // Wait for file timestamps to tick2413 // Wait for file timestamps to tick
1423 const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir);2414 const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir);
1424 while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) {2415 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);
1426 }2417 }
14272418
1428 var digest1: HexDigest = undefined;2419 var digest1: HexDigest = undefined;
...@@ -1435,47 +2426,57 @@ test "check that changing a file makes cache fail" {...@@ -1435,47 +2426,57 @@ test "check that changing a file makes cache fail" {
1435 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),2426 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
1436 .cwd = cwd,2427 .cwd = cwd,
1437 };2428 };
1438 cache.addPrefix(.{ .path = null, .handle = tmp.dir });2429 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
2430 cache.addPrefix(tmp_directory);
1439 defer cache.manifest_dir.close(io);2431 defer cache.manifest_dir.close(io);
14402432
1441 {2433 {
1442 var ch = cache.obtain();2434 var man = cache.obtain();
1443 defer ch.deinit();2435 defer man.deinit();
14442436
1445 ch.hash.addBytes("1234");2437 man.hash.addBytes("1234");
1446 const temp_file_idx = try ch.addFilePath(.initCwd(temp_file), 100);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 cache2443 var diag: Manifest.CheckDiagnostic = undefined;
1449 try testing.expectEqual(false, try ch.hit(.none));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();
1456 }2451 }
14572452
1458 try tmp.dir.writeFile(io, .{ .sub_path = temp_file, .data = updated_temp_file_contents });2453 try tmp.dir.writeFile(io, .{ .sub_path = temp_file, .data = updated_temp_file_contents });
14592454
1460 {2455 {
1461 var ch = cache.obtain();2456 var man = cache.obtain();
1462 defer ch.deinit();2457 defer man.deinit();
14632458
1464 ch.hash.addBytes("1234");2459 man.hash.addBytes("1234");
1465 const temp_file_idx = try ch.addFilePath(.initCwd(temp_file), 100);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 it2465 // The one input file changed.
1468 try testing.expectEqual(false, try ch.hit(.none));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.2472 try testing.expectEqualStrings(updated_temp_file_contents, temp_file_idx.contents(&man));
1471 try testing.expect(ch.files.keys()[temp_file_idx].contents == null);
14722473
1473 digest2 = ch.final();2474 digest2 = man.missDigestHex();
14742475
1475 try ch.writeManifest();2476 try man.finalize();
1476 }2477 }
14772478
1478 try testing.expect(!mem.eql(u8, digest1[0..], digest2[0..]));2479 try testing.expect(!mem.eql(u8, &digest1, &digest2));
1479 }2480 }
1480}2481}
14812482
...@@ -1508,12 +2509,12 @@ test "no file inputs" {...@@ -1508,12 +2509,12 @@ test "no file inputs" {
15082509
1509 man.hash.addBytes("1234");2510 man.hash.addBytes("1234");
15102511
1511 // There should be nothing in the cache2512 var diag: Manifest.CheckDiagnostic = undefined;
1512 try testing.expectEqual(false, try man.hit(.none));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();
1517 }2518 }
1518 {2519 {
1519 var man = cache.obtain();2520 var man = cache.obtain();
...@@ -1521,15 +2522,16 @@ test "no file inputs" {...@@ -1521,15 +2522,16 @@ test "no file inputs" {
15212522
1522 man.hash.addBytes("1234");2523 man.hash.addBytes("1234");
15232524
1524 try testing.expect(try man.hit(.none));2525 var diag: Manifest.CheckDiagnostic = undefined;
1525 digest2 = man.final();2526 try testing.expectEqual(.hit, try man.check(&diag, .none));
2527 digest2 = man.hitDigestHex();
1526 try testing.expectEqual(false, man.have_exclusive_lock);2528 try testing.expectEqual(false, man.have_exclusive_lock);
1527 }2529 }
15282530
1529 try testing.expectEqual(digest1, digest2);2531 try testing.expectEqual(digest1, digest2);
1530}2532}
15312533
1532test "Manifest with files added after initial hash work" {2534test "Manifest with files added after initial hash" {
1533 const io = testing.io;2535 const io = testing.io;
15342536
1535 var tmp = testing.tmpDir(.{});2537 var tmp = testing.tmpDir(.{});
...@@ -1544,11 +2546,16 @@ test "Manifest with files added after initial hash work" {...@@ -1544,11 +2546,16 @@ test "Manifest with files added after initial hash work" {
15442546
1545 try tmp.dir.writeFile(io, .{ .sub_path = temp_file1, .data = "Hello, world!\n" });2547 try tmp.dir.writeFile(io, .{ .sub_path = temp_file1, .data = "Hello, world!\n" });
1546 try tmp.dir.writeFile(io, .{ .sub_path = temp_file2, .data = "Hello world the second!\n" });2548 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
1548 // Wait for file timestamps to tick2555 // Wait for file timestamps to tick
1549 const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir);2556 const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir);
1550 while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) {2557 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);
1552 }2559 }
15532560
1554 var digest1: HexDigest = undefined;2561 var digest1: HexDigest = undefined;
...@@ -1562,35 +2569,46 @@ test "Manifest with files added after initial hash work" {...@@ -1562,35 +2569,46 @@ test "Manifest with files added after initial hash work" {
1562 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),2569 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
1563 .cwd = cwd,2570 .cwd = cwd,
1564 };2571 };
1565 cache.addPrefix(.{ .path = null, .handle = tmp.dir });2572 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
2573 cache.addPrefix(tmp_directory);
1566 defer cache.manifest_dir.close(io);2574 defer cache.manifest_dir.close(io);
15672575
1568 {2576 {
1569 var ch = cache.obtain();2577 var man = cache.obtain();
1570 defer ch.deinit();2578 defer man.deinit();
15712579
1572 ch.hash.addBytes("1234");2580 man.hash.addBytes("1234");
1573 _ = try ch.addFilePath(.initCwd(temp_file1), null);2581 _ = try man.addInputPath(.{
2582 .root_dir = tmp_directory,
2583 .sub_path = temp_file1,
2584 }, .{});
15742585
1575 // There should be nothing in the cache2586 var diag: Manifest.CheckDiagnostic = undefined;
1576 try testing.expectEqual(false, try ch.hit(.none));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();2594 digest1 = man.missDigestHex();
1581 try ch.writeManifest();2595 try man.finalize();
1582 }2596 }
1583 {2597 {
1584 var ch = cache.obtain();2598 var man = cache.obtain();
1585 defer ch.deinit();2599 defer man.deinit();
15862600
1587 ch.hash.addBytes("1234");2601 man.hash.addBytes("1234");
1588 _ = try ch.addFilePath(.initCwd(temp_file1), null);2602 _ = try man.addInputPath(.{
2603 .root_dir = tmp_directory,
2604 .sub_path = temp_file1,
2605 }, .{});
15892606
1590 try testing.expect(try ch.hit(.none));2607 var diag: Manifest.CheckDiagnostic = undefined;
1591 digest2 = ch.final();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);
1594 }2612 }
1595 try testing.expect(mem.eql(u8, &digest1, &digest2));2613 try testing.expect(mem.eql(u8, &digest1, &digest2));
15962614
...@@ -1600,26 +2618,43 @@ test "Manifest with files added after initial hash work" {...@@ -1600,26 +2618,43 @@ test "Manifest with files added after initial hash work" {
1600 // Wait for file timestamps to tick2618 // Wait for file timestamps to tick
1601 const initial_time2 = try testGetCurrentFileTimestamp(io, tmp.dir);2619 const initial_time2 = try testGetCurrentFileTimestamp(io, tmp.dir);
1602 while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time2.nanoseconds) {2620 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);
1604 }2622 }
16052623
1606 {2624 {
1607 var ch = cache.obtain();2625 var man = cache.obtain();
1608 defer ch.deinit();2626 defer man.deinit();
16092627
1610 ch.hash.addBytes("1234");2628 man.hash.addBytes("1234");
1611 _ = try ch.addFilePath(.initCwd(temp_file1), null);2629 _ = try man.addInputPath(.{
16122630 .root_dir = tmp_directory,
1613 // A file that we depend on has been updated, so the cache should not contain an entry for it2631 .sub_path = temp_file1,
1614 try testing.expectEqual(false, try ch.hit(.none));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();
1621 }2650 }
16222651
1623 try testing.expect(!mem.eql(u8, &digest1, &digest3));2652 try testing.expect(!mem.eql(u8, &digest1, &digest3));
1624 }2653 }
1625}2654}
2655
2656test {
2657 _ = Path;
2658 _ = Directory;
2659 _ = DepTokenizer;
2660}
lib/std/Build/Cache/DepTokenizer.zig+188-250
...@@ -1,14 +1,15 @@...@@ -1,14 +1,15 @@
1const Tokenizer = @This();1const Tokenizer = @This();
22
3index: usize = 0,
4bytes: []const u8,
5state: State = .lhs,
6
7const std = @import("std");3const std = @import("std");
4const Io = std.Io;
8const testing = std.testing;5const testing = std.testing;
9const assert = std.debug.assert;6const assert = std.debug.assert;
10const Allocator = std.mem.Allocator;7const Allocator = std.mem.Allocator;
118
9index: usize = 0,
10bytes: []const u8,
11state: State = .lhs,
12
12pub fn next(self: *Tokenizer) ?Token {13pub fn next(self: *Tokenizer) ?Token {
13 var start = self.index;14 var start = self.index;
14 var must_resolve = false;15 var must_resolve = false;
...@@ -16,10 +17,7 @@ pub fn next(self: *Tokenizer) ?Token {...@@ -16,10 +17,7 @@ pub fn next(self: *Tokenizer) ?Token {
16 const char = self.bytes[self.index];17 const char = self.bytes[self.index];
17 switch (self.state) {18 switch (self.state) {
18 .lhs => switch (char) {19 .lhs => switch (char) {
19 '\t', '\n', '\r', ' ' => {20 '\t', '\n', '\r', ' ' => self.index += 1, // Ignore whitespace.
20 // silently ignore whitespace
21 self.index += 1;
22 },
23 else => {21 else => {
24 start = self.index;22 start = self.index;
25 self.state = .target;23 self.state = .target;
...@@ -79,7 +77,7 @@ pub fn next(self: *Tokenizer) ?Token {...@@ -79,7 +77,7 @@ pub fn next(self: *Tokenizer) ?Token {
79 self.index += 1;77 self.index += 1;
80 },78 },
81 else => {79 else => {
82 return errorIllegalChar(.expected_dollar_sign, self.index, char);80 return errorIllegalChar(.expected_dollar, self.index, char);
83 },81 },
84 },82 },
85 .target_colon => switch (char) {83 .target_colon => switch (char) {
...@@ -347,7 +345,7 @@ pub const Token = union(enum) {...@@ -347,7 +345,7 @@ pub const Token = union(enum) {
347345
348 invalid_target: IndexAndChar,346 invalid_target: IndexAndChar,
349 bad_target_escape: IndexAndChar,347 bad_target_escape: IndexAndChar,
350 expected_dollar_sign: IndexAndChar,348 expected_dollar: IndexAndChar,
351 continuation_eol: IndexAndChar,349 continuation_eol: IndexAndChar,
352 incomplete_escape: IndexAndChar,350 incomplete_escape: IndexAndChar,
353 expected_colon: IndexAndChar,351 expected_colon: IndexAndChar,
...@@ -429,48 +427,23 @@ pub const Token = union(enum) {...@@ -429,48 +427,23 @@ pub const Token = union(enum) {
429 }427 }
430 }428 }
431429
432 pub fn printError(self: Token, gpa: Allocator, list: *std.ArrayList(u8)) error{OutOfMemory}!void {430 pub fn format(token: Token, w: *Io.Writer) Io.Writer.Error!void {
433 switch (self) {431 switch (token) {
434 .target, .target_must_resolve, .prereq, .prereq_must_resolve => unreachable, // not an error432 .target, .target_must_resolve, .prereq, .prereq_must_resolve => return w.writeAll(@tagName(token)),
435 .incomplete_quoted_prerequisite,433 .incomplete_quoted_prerequisite, .incomplete_target => |info| return w.print("{t} {q} at position {d}", .{
436 .incomplete_target,434 token, info.bytes, info.index,
437 => |index_and_bytes| {435 }),
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 },
447 .invalid_target,436 .invalid_target,
448 .bad_target_escape,437 .bad_target_escape,
449 .expected_dollar_sign,438 .expected_dollar,
450 .continuation_eol,439 .continuation_eol,
451 .incomplete_escape,440 .incomplete_escape,
452 .expected_colon,441 .expected_colon,
453 => |index_and_char| {442 => |info| return w.print("illegal char '{f}' at position {d}: {t}", .{
454 try list.appendSlice(gpa, "illegal char ");443 std.zig.fmtChar(info.char), info.index, token,
455 try printUnderstandableChar(gpa, list, index_and_char.char);444 }),
456 try list.print(gpa, " at position {d}: {s}", .{ index_and_char.index, self.errStr() });
457 },
458 }445 }
459 }446 }
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 }
474};447};
475448
476test "empty file" {449test "empty file" {
...@@ -493,37 +466,37 @@ test "empty colon" {...@@ -493,37 +466,37 @@ test "empty colon" {
493}466}
494467
495test "empty target" {468test "empty target" {
496 try depTokenizer("foo.o:", "target = {foo.o}");469 try depTokenizer("foo.o:", "target = {\"foo.o\"}");
497 try depTokenizer(470 try depTokenizer(
498 \\foo.o:471 \\foo.o:
499 \\bar.o:472 \\bar.o:
500 \\abcd.o:473 \\abcd.o:
501 ,474 ,
502 \\target = {foo.o}475 \\target = {"foo.o"}
503 \\target = {bar.o}476 \\target = {"bar.o"}
504 \\target = {abcd.o}477 \\target = {"abcd.o"}
505 );478 );
506}479}
507480
508test "whitespace empty target" {481test "whitespace empty target" {
509 try depTokenizer("\nfoo.o:", "target = {foo.o}");482 try depTokenizer("\nfoo.o:", "target = {\"foo.o\"}");
510 try depTokenizer("\rfoo.o:", "target = {foo.o}");483 try depTokenizer("\rfoo.o:", "target = {\"foo.o\"}");
511 try depTokenizer("\r\nfoo.o:", "target = {foo.o}");484 try depTokenizer("\r\nfoo.o:", "target = {\"foo.o\"}");
512 try depTokenizer(" foo.o:", "target = {foo.o}");485 try depTokenizer(" foo.o:", "target = {\"foo.o\"}");
513}486}
514487
515test "escape empty target" {488test "escape empty target" {
516 try depTokenizer("\\ foo.o:", "target = { foo.o}");489 try depTokenizer("\\ foo.o:", "target = {\" foo.o\"}");
517 try depTokenizer("\\#foo.o:", "target = {#foo.o}");490 try depTokenizer("\\#foo.o:", "target = {\"#foo.o\"}");
518 try depTokenizer("\\\\foo.o:", "target = {\\foo.o}");491 try depTokenizer("\\\\foo.o:", "target = {\"\\\\foo.o\"}");
519 try depTokenizer("$$foo.o:", "target = {$foo.o}");492 try depTokenizer("$$foo.o:", "target = {\"$foo.o\"}");
520}493}
521494
522test "empty target linefeeds" {495test "empty target linefeeds" {
523 try depTokenizer("\n", "");496 try depTokenizer("\n", "");
524 try depTokenizer("\r\n", "");497 try depTokenizer("\r\n", "");
525498
526 const expect = "target = {foo.o}";499 const expect = "target = {\"foo.o\"}";
527 try depTokenizer(500 try depTokenizer(
528 \\foo.o:501 \\foo.o:
529 , expect);502 , expect);
...@@ -541,7 +514,7 @@ test "empty target linefeeds" {...@@ -541,7 +514,7 @@ test "empty target linefeeds" {
541}514}
542515
543test "empty target linefeeds + continuations" {516test "empty target linefeeds + continuations" {
544 const expect = "target = {foo.o}";517 const expect = "target = {\"foo.o\"}";
545 try depTokenizer(518 try depTokenizer(
546 \\foo.o:\519 \\foo.o:\
547 , expect);520 , expect);
...@@ -559,7 +532,7 @@ test "empty target linefeeds + continuations" {...@@ -559,7 +532,7 @@ test "empty target linefeeds + continuations" {
559}532}
560533
561test "empty target linefeeds + hspace + continuations" {534test "empty target linefeeds + hspace + continuations" {
562 const expect = "target = {foo.o}";535 const expect = "target = {\"foo.o\"}";
563 try depTokenizer(536 try depTokenizer(
564 \\foo.o: \537 \\foo.o: \
565 , expect);538 , expect);
...@@ -577,7 +550,7 @@ test "empty target linefeeds + hspace + continuations" {...@@ -577,7 +550,7 @@ test "empty target linefeeds + hspace + continuations" {
577}550}
578551
579test "empty target + hspace + colon" {552test "empty target + hspace + colon" {
580 const expect = "target = {foo.o}";553 const expect = "target = {\"foo.o\"}";
581554
582 try depTokenizer("foo.o :", expect);555 try depTokenizer("foo.o :", expect);
583 try depTokenizer("foo.o\t\t\t:", expect);556 try depTokenizer("foo.o\t\t\t:", expect);
...@@ -588,8 +561,8 @@ test "empty target + hspace + colon" {...@@ -588,8 +561,8 @@ test "empty target + hspace + colon" {
588561
589test "prereq" {562test "prereq" {
590 const expect =563 const expect =
591 \\target = {foo.o}564 \\target = {"foo.o"}
592 \\prereq = {foo.c}565 \\prereq = {"foo.c"}
593 ;566 ;
594 try depTokenizer("foo.o: foo.c", expect);567 try depTokenizer("foo.o: foo.c", expect);
595 try depTokenizer(568 try depTokenizer(
...@@ -608,9 +581,9 @@ test "prereq" {...@@ -608,9 +581,9 @@ test "prereq" {
608581
609test "prereq continuation" {582test "prereq continuation" {
610 const expect =583 const expect =
611 \\target = {foo.o}584 \\target = {"foo.o"}
612 \\prereq = {foo.h}585 \\prereq = {"foo.h"}
613 \\prereq = {bar.h}586 \\prereq = {"bar.h"}
614 ;587 ;
615 try depTokenizer(588 try depTokenizer(
616 \\foo.o: foo.h\589 \\foo.o: foo.h\
...@@ -624,19 +597,19 @@ test "prereq continuation" {...@@ -624,19 +597,19 @@ test "prereq continuation" {
624597
625test "prereq continuation (CRLF)" {598test "prereq continuation (CRLF)" {
626 const expect =599 const expect =
627 \\target = {foo.o}600 \\target = {"foo.o"}
628 \\prereq = {foo.h}601 \\prereq = {"foo.h"}
629 \\prereq = {bar.h}602 \\prereq = {"bar.h"}
630 ;603 ;
631 try depTokenizer("foo.o: foo.h\\\r\nbar.h", expect);604 try depTokenizer("foo.o: foo.h\\\r\nbar.h", expect);
632}605}
633606
634test "multiple prereqs" {607test "multiple prereqs" {
635 const expect =608 const expect =
636 \\target = {foo.o}609 \\target = {"foo.o"}
637 \\prereq = {foo.c}610 \\prereq = {"foo.c"}
638 \\prereq = {foo.h}611 \\prereq = {"foo.h"}
639 \\prereq = {bar.h}612 \\prereq = {"bar.h"}
640 ;613 ;
641 try depTokenizer("foo.o: foo.c foo.h bar.h", expect);614 try depTokenizer("foo.o: foo.c foo.h bar.h", expect);
642 try depTokenizer(615 try depTokenizer(
...@@ -680,19 +653,19 @@ test "multiple targets and prereqs" {...@@ -680,19 +653,19 @@ test "multiple targets and prereqs" {
680 \\ one.h two.h \653 \\ one.h two.h \
681 \\ three.h four.h654 \\ three.h four.h
682 ,655 ,
683 \\target = {foo.o}656 \\target = {"foo.o"}
684 \\prereq = {foo.c}657 \\prereq = {"foo.c"}
685 \\target = {bar.o}658 \\target = {"bar.o"}
686 \\prereq = {bar.c}659 \\prereq = {"bar.c"}
687 \\prereq = {a.h}660 \\prereq = {"a.h"}
688 \\prereq = {b.h}661 \\prereq = {"b.h"}
689 \\prereq = {c.h}662 \\prereq = {"c.h"}
690 \\target = {abc.o}663 \\target = {"abc.o"}
691 \\prereq = {abc.c}664 \\prereq = {"abc.c"}
692 \\prereq = {one.h}665 \\prereq = {"one.h"}
693 \\prereq = {two.h}666 \\prereq = {"two.h"}
694 \\prereq = {three.h}667 \\prereq = {"three.h"}
695 \\prereq = {four.h}668 \\prereq = {"four.h"}
696 );669 );
697 try depTokenizer(670 try depTokenizer(
698 \\ascii.o: ascii.c671 \\ascii.o: ascii.c
...@@ -702,21 +675,21 @@ test "multiple targets and prereqs" {...@@ -702,21 +675,21 @@ test "multiple targets and prereqs" {
702 \\ macho.c\675 \\ macho.c\
703 \\ a.h b.h c.h676 \\ a.h b.h c.h
704 ,677 ,
705 \\target = {ascii.o}678 \\target = {"ascii.o"}
706 \\prereq = {ascii.c}679 \\prereq = {"ascii.c"}
707 \\target = {base64.o}680 \\target = {"base64.o"}
708 \\prereq = {base64.c}681 \\prereq = {"base64.c"}
709 \\prereq = {stdio.h}682 \\prereq = {"stdio.h"}
710 \\target = {elf.o}683 \\target = {"elf.o"}
711 \\prereq = {elf.c}684 \\prereq = {"elf.c"}
712 \\prereq = {a.h}685 \\prereq = {"a.h"}
713 \\prereq = {b.h}686 \\prereq = {"b.h"}
714 \\prereq = {c.h}687 \\prereq = {"c.h"}
715 \\target = {macho.o}688 \\target = {"macho.o"}
716 \\prereq = {macho.c}689 \\prereq = {"macho.c"}
717 \\prereq = {a.h}690 \\prereq = {"a.h"}
718 \\prereq = {b.h}691 \\prereq = {"b.h"}
719 \\prereq = {c.h}692 \\prereq = {"c.h"}
720 );693 );
721 try depTokenizer(694 try depTokenizer(
722 \\a$$scii.o: ascii.c695 \\a$$scii.o: ascii.c
...@@ -726,20 +699,20 @@ test "multiple targets and prereqs" {...@@ -726,20 +699,20 @@ test "multiple targets and prereqs" {
726 \\ "macho!.c" \699 \\ "macho!.c" \
727 \\ a.h b.h c.h700 \\ a.h b.h c.h
728 ,701 ,
729 \\target = {a$scii.o}702 \\target = {"a$scii.o"}
730 \\prereq = {ascii.c}703 \\prereq = {"ascii.c"}
731 \\target = {\base64.o}704 \\target = {"\\base64.o"}
732 \\prereq = {\base64.c}705 \\prereq = {"\\base64.c"}
733 \\prereq = {s t#dio.h}706 \\prereq = {"s t#dio.h"}
734 \\target = {e\lf.o}707 \\target = {"e\\lf.o"}
735 \\prereq = {e\lf.c}708 \\prereq = {"e\\lf.c"}
736 \\prereq = {a.h$$}709 \\prereq = {"a.h$$"}
737 \\prereq = {$$b.h c.h$$}710 \\prereq = {"$$b.h c.h$$"}
738 \\target = {macho.o}711 \\target = {"macho.o"}
739 \\prereq = {macho!.c}712 \\prereq = {"macho!.c"}
740 \\prereq = {a.h}713 \\prereq = {"a.h"}
741 \\prereq = {b.h}714 \\prereq = {"b.h"}
742 \\prereq = {c.h}715 \\prereq = {"c.h"}
743 );716 );
744}717}
745718
...@@ -750,12 +723,12 @@ test "windows quoted prereqs" {...@@ -750,12 +723,12 @@ test "windows quoted prereqs" {
750 \\ "C:\Program Files (x86)\Microsoft Visual Studio\foo1.h" \723 \\ "C:\Program Files (x86)\Microsoft Visual Studio\foo1.h" \
751 \\ "C:\Program Files (x86)\Microsoft Visual Studio\foo2.h"724 \\ "C:\Program Files (x86)\Microsoft Visual Studio\foo2.h"
752 ,725 ,
753 \\target = {c:\foo.o}726 \\target = {"c:\\foo.o"}
754 \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\foo.c}727 \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\foo.c"}
755 \\target = {c:\foo2.o}728 \\target = {"c:\\foo2.o"}
756 \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\foo2.c}729 \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\foo2.c"}
757 \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\foo1.h}730 \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\foo1.h"}
758 \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\foo2.h}731 \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\foo2.h"}
759 );732 );
760}733}
761734
...@@ -779,23 +752,23 @@ test "windows mixed prereqs" {...@@ -779,23 +752,23 @@ test "windows mixed prereqs" {
779 \\ "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.21.27702\lib\x64\\..\..\include\vcruntime_string.h" \752 \\ "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.21.27702\lib\x64\\..\..\include\vcruntime_string.h" \
780 \\ "C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\corecrt_wstring.h"753 \\ "C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\corecrt_wstring.h"
781 ,754 ,
782 \\target = {cimport.o}755 \\target = {"cimport.o"}
783 \\prereq = {C:\msys64\home\anon\project\zig\master\zig-cache\o\qhvhbUo7GU5iKyQ5mpA8TcQpncCYaQu0wwvr3ybiSTj_Dtqi1Nmcb70kfODJ2Qlg\cimport.h}756 \\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}757 \\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}758 \\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}759 \\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}760 \\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}761 \\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}762 \\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}763 \\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}764 \\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}765 \\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}766 \\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}767 \\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}768 \\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}769 \\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}770 \\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}771 \\prereq = {"C:\\Program Files (x86)\\Windows Kits\\10\\\\Include\\10.0.17763.0\\ucrt\\corecrt_wstring.h"}
799 );772 );
800}773}
801774
...@@ -810,14 +783,14 @@ test "windows funky targets" {...@@ -810,14 +783,14 @@ test "windows funky targets" {
810 \\C:\Users\anon\$$foo.o:783 \\C:\Users\anon\$$foo.o:
811 \\C:\Users\anon\\\ \ \ \ \ foo.o:784 \\C:\Users\anon\\\ \ \ \ \ foo.o:
812 ,785 ,
813 \\target = {C:\Users\anon\foo.o}786 \\target = {"C:\\Users\\anon\\foo.o"}
814 \\target = {C:\Users\anon\foo .o}787 \\target = {"C:\\Users\\anon\\foo .o"}
815 \\target = {C:\Users\anon\foo#.o}788 \\target = {"C:\\Users\\anon\\foo#.o"}
816 \\target = {C:\Users\anon\foo$.o}789 \\target = {"C:\\Users\\anon\\foo$.o"}
817 \\target = {C:\Users\anon\ foo.o}790 \\target = {"C:\\Users\\anon\\ foo.o"}
818 \\target = {C:\Users\anon\#foo.o}791 \\target = {"C:\\Users\\anon\\#foo.o"}
819 \\target = {C:\Users\anon\$foo.o}792 \\target = {"C:\\Users\\anon\\$foo.o"}
820 \\target = {C:\Users\anon\ foo.o}793 \\target = {"C:\\Users\\anon\\ foo.o"}
821 );794 );
822}795}
823796
...@@ -837,13 +810,13 @@ test "windows funky prereqs" {...@@ -837,13 +810,13 @@ test "windows funky prereqs" {
837 \\ somedir\\ \\ \c.c\810 \\ somedir\\ \\ \c.c\
838 \\811 \\
839 ,812 ,
840 \\target = {cimport.o}813 \\target = {"cimport.o"}
841 \\prereq = {trailingbackslash\}814 \\prereq = {"trailingbackslash\\"}
842 \\prereq = {C:\Users\John Smith\AppData\Local\zig\p\1220d14057af1a9d6dde4643293527bd5ee5099517d655251a066666a4320737ea7c\cimport.c}815 \\prereq = {"C:\\Users\\John Smith\\AppData\\Local\\zig\\p\\1220d14057af1a9d6dde4643293527bd5ee5099517d655251a066666a4320737ea7c\\cimport.c"}
843 \\prereq = {somedir\ a.c}816 \\prereq = {"somedir\\ a.c"}
844 \\prereq = {somedir/ a.c}817 \\prereq = {"somedir/ a.c"}
845 \\prereq = {somedir\ b.c}818 \\prereq = {"somedir\\ b.c"}
846 \\prereq = {somedir\ \ \c.c}819 \\prereq = {"somedir\\ \\ \\c.c"}
847 );820 );
848}821}
849822
...@@ -852,171 +825,171 @@ test "windows drive and forward slashes" {...@@ -852,171 +825,171 @@ test "windows drive and forward slashes" {
852 \\C:/msys64/what/zig-cache\tmp\48ac4d78dd531abd-cxa_thread_atexit.obj: \825 \\C:/msys64/what/zig-cache\tmp\48ac4d78dd531abd-cxa_thread_atexit.obj: \
853 \\ C:/msys64/opt/zig3/lib/zig/libc/mingw/crt/cxa_thread_atexit.c826 \\ C:/msys64/opt/zig3/lib/zig/libc/mingw/crt/cxa_thread_atexit.c
854 ,827 ,
855 \\target = {C:/msys64/what/zig-cache\tmp\48ac4d78dd531abd-cxa_thread_atexit.obj}828 \\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}829 \\prereq = {"C:/msys64/opt/zig3/lib/zig/libc/mingw/crt/cxa_thread_atexit.c"}
857 );830 );
858}831}
859832
860test "error incomplete escape - reverse_solidus" {833test "error incomplete escape - reverse_solidus" {
861 try depTokenizer("\\",834 try depTokenizer("\\",
862 \\ERROR: illegal char '\' at position 0: incomplete escape835 \\ERROR: illegal char '\\' at position 0: incomplete_escape
863 );836 );
864 try depTokenizer("\t\\",837 try depTokenizer("\t\\",
865 \\ERROR: illegal char '\' at position 1: incomplete escape838 \\ERROR: illegal char '\\' at position 1: incomplete_escape
866 );839 );
867 try depTokenizer("\n\\",840 try depTokenizer("\n\\",
868 \\ERROR: illegal char '\' at position 1: incomplete escape841 \\ERROR: illegal char '\\' at position 1: incomplete_escape
869 );842 );
870 try depTokenizer("\r\\",843 try depTokenizer("\r\\",
871 \\ERROR: illegal char '\' at position 1: incomplete escape844 \\ERROR: illegal char '\\' at position 1: incomplete_escape
872 );845 );
873 try depTokenizer("\r\n\\",846 try depTokenizer("\r\n\\",
874 \\ERROR: illegal char '\' at position 2: incomplete escape847 \\ERROR: illegal char '\\' at position 2: incomplete_escape
875 );848 );
876 try depTokenizer(" \\",849 try depTokenizer(" \\",
877 \\ERROR: illegal char '\' at position 1: incomplete escape850 \\ERROR: illegal char '\\' at position 1: incomplete_escape
878 );851 );
879}852}
880853
881test "error incomplete escape - dollar_sign" {854test "error incomplete escape - dollar_sign" {
882 try depTokenizer("$",855 try depTokenizer("$",
883 \\ERROR: illegal char '$' at position 0: incomplete escape856 \\ERROR: illegal char '$' at position 0: incomplete_escape
884 );857 );
885 try depTokenizer("\t$",858 try depTokenizer("\t$",
886 \\ERROR: illegal char '$' at position 1: incomplete escape859 \\ERROR: illegal char '$' at position 1: incomplete_escape
887 );860 );
888 try depTokenizer("\n$",861 try depTokenizer("\n$",
889 \\ERROR: illegal char '$' at position 1: incomplete escape862 \\ERROR: illegal char '$' at position 1: incomplete_escape
890 );863 );
891 try depTokenizer("\r$",864 try depTokenizer("\r$",
892 \\ERROR: illegal char '$' at position 1: incomplete escape865 \\ERROR: illegal char '$' at position 1: incomplete_escape
893 );866 );
894 try depTokenizer("\r\n$",867 try depTokenizer("\r\n$",
895 \\ERROR: illegal char '$' at position 2: incomplete escape868 \\ERROR: illegal char '$' at position 2: incomplete_escape
896 );869 );
897 try depTokenizer(" $",870 try depTokenizer(" $",
898 \\ERROR: illegal char '$' at position 1: incomplete escape871 \\ERROR: illegal char '$' at position 1: incomplete_escape
899 );872 );
900}873}
901874
902test "error incomplete target" {875test "error incomplete target" {
903 try depTokenizer("foo.o",876 try depTokenizer("foo.o",
904 \\ERROR: incomplete target 'foo.o' at position 0877 \\ERROR: incomplete_target "foo.o" at position 0
905 );878 );
906 try depTokenizer("\tfoo.o",879 try depTokenizer("\tfoo.o",
907 \\ERROR: incomplete target 'foo.o' at position 1880 \\ERROR: incomplete_target "foo.o" at position 1
908 );881 );
909 try depTokenizer("\nfoo.o",882 try depTokenizer("\nfoo.o",
910 \\ERROR: incomplete target 'foo.o' at position 1883 \\ERROR: incomplete_target "foo.o" at position 1
911 );884 );
912 try depTokenizer("\rfoo.o",885 try depTokenizer("\rfoo.o",
913 \\ERROR: incomplete target 'foo.o' at position 1886 \\ERROR: incomplete_target "foo.o" at position 1
914 );887 );
915 try depTokenizer("\r\nfoo.o",888 try depTokenizer("\r\nfoo.o",
916 \\ERROR: incomplete target 'foo.o' at position 2889 \\ERROR: incomplete_target "foo.o" at position 2
917 );890 );
918 try depTokenizer(" foo.o",891 try depTokenizer(" foo.o",
919 \\ERROR: incomplete target 'foo.o' at position 1892 \\ERROR: incomplete_target "foo.o" at position 1
920 );893 );
921894
922 try depTokenizer("\\ foo.o",895 try depTokenizer("\\ foo.o",
923 \\ERROR: incomplete target ' foo.o' at position 0896 \\ERROR: incomplete_target "\\ foo.o" at position 0
924 );897 );
925 try depTokenizer("\\#foo.o",898 try depTokenizer("\\#foo.o",
926 \\ERROR: incomplete target '#foo.o' at position 0899 \\ERROR: incomplete_target "\\#foo.o" at position 0
927 );900 );
928 try depTokenizer("\\\\foo.o",901 try depTokenizer("\\\\foo.o",
929 \\ERROR: incomplete target '\foo.o' at position 0902 \\ERROR: incomplete_target "\\\\foo.o" at position 0
930 );903 );
931 try depTokenizer("$$foo.o",904 try depTokenizer("$$foo.o",
932 \\ERROR: incomplete target '$foo.o' at position 0905 \\ERROR: incomplete_target "$$foo.o" at position 0
933 );906 );
934}907}
935908
936test "error illegal char at position - bad target escape" {909test "error illegal char at position - bad target escape" {
937 try depTokenizer("\\\t",910 try depTokenizer("\\\t",
938 \\ERROR: illegal char \x09 at position 1: bad target escape911 \\ERROR: illegal char '\t' at position 1: bad_target_escape
939 );912 );
940 try depTokenizer("\\\n",913 try depTokenizer("\\\n",
941 \\ERROR: illegal char \x0A at position 1: bad target escape914 \\ERROR: illegal char '\n' at position 1: bad_target_escape
942 );915 );
943 try depTokenizer("\\\r",916 try depTokenizer("\\\r",
944 \\ERROR: illegal char \x0D at position 1: bad target escape917 \\ERROR: illegal char '\r' at position 1: bad_target_escape
945 );918 );
946 try depTokenizer("\\\r\n",919 try depTokenizer("\\\r\n",
947 \\ERROR: illegal char \x0D at position 1: bad target escape920 \\ERROR: illegal char '\r' at position 1: bad_target_escape
948 );921 );
949}922}
950923
951test "error illegal char at position - expecting dollar_sign" {924test "error illegal char at position - expecting dollar_sign" {
952 try depTokenizer("$\t",925 try depTokenizer("$\t",
953 \\ERROR: illegal char \x09 at position 1: expecting '$'926 \\ERROR: illegal char '\t' at position 1: expected_dollar
954 );927 );
955 try depTokenizer("$\n",928 try depTokenizer("$\n",
956 \\ERROR: illegal char \x0A at position 1: expecting '$'929 \\ERROR: illegal char '\n' at position 1: expected_dollar
957 );930 );
958 try depTokenizer("$\r",931 try depTokenizer("$\r",
959 \\ERROR: illegal char \x0D at position 1: expecting '$'932 \\ERROR: illegal char '\r' at position 1: expected_dollar
960 );933 );
961 try depTokenizer("$\r\n",934 try depTokenizer("$\r\n",
962 \\ERROR: illegal char \x0D at position 1: expecting '$'935 \\ERROR: illegal char '\r' at position 1: expected_dollar
963 );936 );
964}937}
965938
966test "error illegal char at position - invalid target" {939test "error illegal char at position - invalid target" {
967 try depTokenizer("foo\n.o",940 try depTokenizer("foo\n.o",
968 \\ERROR: illegal char \x0A at position 3: invalid target941 \\ERROR: illegal char '\n' at position 3: invalid_target
969 );942 );
970 try depTokenizer("foo\r.o",943 try depTokenizer("foo\r.o",
971 \\ERROR: illegal char \x0D at position 3: invalid target944 \\ERROR: illegal char '\r' at position 3: invalid_target
972 );945 );
973 try depTokenizer("foo\r\n.o",946 try depTokenizer("foo\r\n.o",
974 \\ERROR: illegal char \x0D at position 3: invalid target947 \\ERROR: illegal char '\r' at position 3: invalid_target
975 );948 );
976}949}
977950
978test "error target - continuation expecting end-of-line" {951test "error target - continuation expecting end-of-line" {
979 try depTokenizer("foo.o: \\\t",952 try depTokenizer("foo.o: \\\t",
980 \\target = {foo.o}953 \\target = {"foo.o"}
981 \\ERROR: illegal char \x09 at position 8: continuation expecting end-of-line954 \\ERROR: illegal char '\t' at position 8: continuation_eol
982 );955 );
983 try depTokenizer("foo.o: \\ ",956 try depTokenizer("foo.o: \\ ",
984 \\target = {foo.o}957 \\target = {"foo.o"}
985 \\ERROR: illegal char ' ' at position 8: continuation expecting end-of-line958 \\ERROR: illegal char ' ' at position 8: continuation_eol
986 );959 );
987 try depTokenizer("foo.o: \\x",960 try depTokenizer("foo.o: \\x",
988 \\target = {foo.o}961 \\target = {"foo.o"}
989 \\ERROR: illegal char 'x' at position 8: continuation expecting end-of-line962 \\ERROR: illegal char 'x' at position 8: continuation_eol
990 );963 );
991 try depTokenizer("foo.o: \\\x0dx",964 try depTokenizer("foo.o: \\\x0dx",
992 \\target = {foo.o}965 \\target = {"foo.o"}
993 \\ERROR: illegal char 'x' at position 9: continuation expecting end-of-line966 \\ERROR: illegal char 'x' at position 9: continuation_eol
994 );967 );
995}968}
996969
997test "error prereq - continuation expecting end-of-line" {970test "error prereq - continuation expecting end-of-line" {
998 try depTokenizer("foo.o: foo.h\\\x0dx",971 try depTokenizer("foo.o: foo.h\\\x0dx",
999 \\target = {foo.o}972 \\target = {"foo.o"}
1000 \\ERROR: illegal char 'x' at position 14: continuation expecting end-of-line973 \\ERROR: illegal char 'x' at position 14: continuation_eol
1001 );974 );
1002}975}
1003976
1004test "error illegal char at position - expecting colon" {977test "error illegal char at position - expecting colon" {
1005 try depTokenizer("foo\t.o:",978 try depTokenizer("foo\t.o:",
1006 \\target = {foo}979 \\target = {"foo"}
1007 \\ERROR: illegal char '.' at position 4: expecting ':'980 \\ERROR: illegal char '.' at position 4: expected_colon
1008 );981 );
1009 try depTokenizer("foo .o:",982 try depTokenizer("foo .o:",
1010 \\target = {foo}983 \\target = {"foo"}
1011 \\ERROR: illegal char '.' at position 4: expecting ':'984 \\ERROR: illegal char '.' at position 4: expected_colon
1012 );985 );
1013 try depTokenizer("foo \n.o:",986 try depTokenizer("foo \n.o:",
1014 \\target = {foo}987 \\target = {"foo"}
1015 \\ERROR: illegal char \x0A at position 4: expecting ':'988 \\ERROR: illegal char '\n' at position 4: expected_colon
1016 );989 );
1017 try depTokenizer("foo.o\t\n:",990 try depTokenizer("foo.o\t\n:",
1018 \\target = {foo.o}991 \\target = {"foo.o"}
1019 \\ERROR: illegal char \x0A at position 6: expecting ':'992 \\ERROR: illegal char '\n' at position 6: expected_colon
1020 );993 );
1021}994}
1022995
...@@ -1028,70 +1001,35 @@ fn depTokenizer(input: []const u8, expect: []const u8) !void {...@@ -1028,70 +1001,35 @@ fn depTokenizer(input: []const u8, expect: []const u8) !void {
10281001
1029 var it: Tokenizer = .{ .bytes = input };1002 var it: Tokenizer = .{ .bytes = input };
1030 var buffer: std.ArrayList(u8) = .empty;1003 var buffer: std.ArrayList(u8) = .empty;
1031 var resolve_buf: std.ArrayList(u8) = .empty;1004 var aux: std.ArrayList(u8) = .empty;
1032 var i: usize = 0;1005 var i: usize = 0;
1033 while (it.next()) |token| {1006 while (it.next()) |token| {
1034 if (i != 0) try buffer.appendSlice(arena, "\n");1007 if (i != 0) try buffer.appendSlice(arena, "\n");
1035 switch (token) {1008 switch (token) {
1036 .target, .prereq => |bytes| {1009 .target, .prereq => |bytes| {
1037 try buffer.appendSlice(arena, @tagName(token));1010 try buffer.print(arena, "{t} = {{{q}}}", .{ token, bytes });
1038 try buffer.appendSlice(arena, " = {");
1039 for (bytes) |b| {
1040 try buffer.append(arena, printable_char_tab[b]);
1041 }
1042 try buffer.appendSlice(arena, "}");
1043 },1011 },
1044 .target_must_resolve => {1012 .target_must_resolve => {
1045 try buffer.appendSlice(arena, "target = {");1013 try buffer.appendSlice(arena, "target = {");
1046 try token.resolve(arena, &resolve_buf);1014 aux.clearRetainingCapacity();
1047 for (resolve_buf.items) |b| {1015 try token.resolve(arena, &aux);
1048 try buffer.append(arena, printable_char_tab[b]);1016 try buffer.print(arena, "{q}", .{aux.items});
1049 }
1050 resolve_buf.items.len = 0;
1051 try buffer.appendSlice(arena, "}");1017 try buffer.appendSlice(arena, "}");
1052 },1018 },
1053 .prereq_must_resolve => {1019 .prereq_must_resolve => {
1054 try buffer.appendSlice(arena, "prereq = {");1020 try buffer.appendSlice(arena, "prereq = {");
1055 try token.resolve(arena, &resolve_buf);1021 aux.clearRetainingCapacity();
1056 for (resolve_buf.items) |b| {1022 try token.resolve(arena, &aux);
1057 try buffer.append(arena, printable_char_tab[b]);1023 try buffer.print(arena, "{q}", .{aux.items});
1058 }
1059 resolve_buf.items.len = 0;
1060 try buffer.appendSlice(arena, "}");1024 try buffer.appendSlice(arena, "}");
1061 },1025 },
1062 else => {1026 else => {
1063 try buffer.appendSlice(arena, "ERROR: ");1027 try buffer.print(arena, "ERROR: {f}", .{token});
1064 try token.printError(arena, &buffer);
1065 break;1028 break;
1066 },1029 },
1067 }1030 }
1068 i += 1;1031 i += 1;
1069 }1032 }
10701033
1071 if (std.mem.eql(u8, expect, buffer.items)) {
1072 try testing.expect(true);
1073 return;
1074 }
1075
1076 try testing.expectEqualStrings(expect, buffer.items);1034 try testing.expectEqualStrings(expect, buffer.items);
1077}1035}
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 {...@@ -1873,12 +1873,11 @@ pub const PathDep = extern struct {
1873 pkg: Package.OptionalIndex,1873 pkg: Package.OptionalIndex,
18741874
1875 pub const Flags = packed struct(u32) {1875 pub const Flags = packed struct(u32) {
1876 mode: Mode,1876 is_directory: bool,
1877 metadata_only: bool,
1877 base: LazyPath.Relative.Base,1878 base: LazyPath.Relative.Base,
1878 _: u16 = 0,1879 _: u22 = 0,
1879 };1880 };
1880
1881 pub const Mode = enum(u8) { directory, contents, metadata };
1882};1881};
18831882
1884pub const InstallDestDir = enum(u32) {1883pub 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...@@ -34,7 +34,8 @@ pub fn write(b: *std.Build, wc: *Configuration.Wip, writer: *std.Io.Writer) !voi
34 .cwd_relative => .cwd,34 .cwd_relative => .cwd,
35 .relative => |r| r.base,35 .relative => |r| r.base,
36 },36 },
37 .mode = src.mode,37 .is_directory = src.is_directory,
38 .metadata_only = src.metadata_only,
38 },39 },
39 .sub = switch (src.lazy_path) {40 .sub = switch (src.lazy_path) {
40 .src_path => |sp| try wc.addString(sp.sub_path),41 .src_path => |sp| try wc.addString(sp.sub_path),
lib/std/Io/Reader.zig+1
...@@ -397,6 +397,7 @@ pub fn appendRemainingAligned(...@@ -397,6 +397,7 @@ pub fn appendRemainingAligned(
397pub const UnlimitedAllocError = Allocator.Error || ShortError;397pub const UnlimitedAllocError = Allocator.Error || ShortError;
398398
399pub fn appendRemainingUnlimited(r: *Reader, gpa: Allocator, list: *ArrayList(u8)) UnlimitedAllocError!void {399pub fn appendRemainingUnlimited(r: *Reader, gpa: Allocator, list: *ArrayList(u8)) UnlimitedAllocError!void {
400 list.pointer_stability.assertUnlocked();
400 var a: std.Io.Writer.Allocating = .initOwnedSlice(gpa, list.allocatedSlice());401 var a: std.Io.Writer.Allocating = .initOwnedSlice(gpa, list.allocatedSlice());
401 a.writer.end = list.items.len;402 a.writer.end = list.items.len;
402 list.* = .empty;403 list.* = .empty;
lib/std/array_list.zig+3
...@@ -776,6 +776,9 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type {...@@ -776,6 +776,9 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type {
776 ///776 ///
777 /// Asserts what the capacity is equal to the length.777 /// Asserts what the capacity is equal to the length.
778 /// Never invalidates element pointers.778 /// Never invalidates element pointers.
779 ///
780 /// See also:
781 /// * `shrinkToLen`
779 pub fn toOwnedSliceAssert(self: *Self) Slice {782 pub fn toOwnedSliceAssert(self: *Self) Slice {
780 assert(self.items.len == self.capacity);783 assert(self.items.len == self.capacity);
781 const items = self.items;784 const items = self.items;
lib/std/testing.zig+3
...@@ -592,6 +592,9 @@ pub const TmpDir = struct {...@@ -592,6 +592,9 @@ pub const TmpDir = struct {
592 const random_bytes_count = 12;592 const random_bytes_count = 12;
593 const sub_path_len = std.base64.url_safe.Encoder.calcSize(random_bytes_count);593 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
595 pub fn cleanup(self: *TmpDir) void {598 pub fn cleanup(self: *TmpDir) void {
596 self.dir.close(io);599 self.dir.close(io);
597 self.parent_dir.deleteTree(io, &self.sub_path) catch {};600 self.parent_dir.deleteTree(io, &self.sub_path) catch {};
lib/std/zig.zig+6-6
...@@ -1838,12 +1838,12 @@ pub fn buildExeSubprocess(...@@ -1838,12 +1838,12 @@ pub fn buildExeSubprocess(
1838 var it = mem.splitScalar(u8, body, 0);1838 var it = mem.splitScalar(u8, body, 0);
1839 while (it.next()) |prefixed_path| {1839 while (it.next()) |prefixed_path| {
1840 const prefix: Server.Message.PathPrefix = @fromBackingInt(@intCast(prefixed_path[0] - 1));1840 const prefix: Server.Message.PathPrefix = @fromBackingInt(@intCast(prefixed_path[0] - 1));
1841 const sub_path = try gpa.dupe(u8, prefixed_path[1..]);1841 const sub_path = prefixed_path[1..];
1842 var keep = false;1842 man.addDiscoveredPath(.{
1843 defer if (!keep) gpa.free(sub_path);1843 .discovered_path = .{ .prefixed = .{
1844 keep = man.addPrefixedPathPost(.{1844 .prefix = @intCast(@backingInt(prefix)),
1845 .prefix = @backingInt(prefix),1845 .sub_path = sub_path,
1846 .sub_path = sub_path,1846 } },
1847 }) catch |err| switch (err) {1847 }) catch |err| switch (err) {
1848 error.Canceled, error.OutOfMemory => |e| return e,1848 error.Canceled, error.OutOfMemory => |e| return e,
1849 else => |e| {1849 else => |e| {
src/Compilation.zig+148-132
...@@ -701,39 +701,12 @@ pub const Path = struct {...@@ -701,39 +701,12 @@ pub const Path = struct {
701 };701 };
702 }702 }
703703
704 pub fn addToCacheManifestPostHit(p: Path, man: *Cache.Manifest, dirs: *const std.zig.Directories) !void {704 pub fn addToCacheManifestAsDiscovered(
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(
732 p: Path,705 p: Path,
733 man: *Cache.Manifest,706 man: *Cache.Manifest,
734 dirs: *const std.zig.Directories,707 dirs: *const std.zig.Directories,
735 bytes: []const u8,708 contents: ?[]const u8,
736 stat: Cache.File.Stat,709 stat: ?Cache.Manifest.Stat,
737 ) !void {710 ) !void {
738 comptime assert(0 == @backingInt(std.zig.Server.Message.PathPrefix.cwd));711 comptime assert(0 == @backingInt(std.zig.Server.Message.PathPrefix.cwd));
739 comptime assert(1 == @backingInt(std.zig.Server.Message.PathPrefix.zig_lib));712 comptime assert(1 == @backingInt(std.zig.Server.Message.PathPrefix.zig_lib));
...@@ -741,24 +714,29 @@ pub const Path = struct {...@@ -741,24 +714,29 @@ pub const Path = struct {
741 comptime assert(3 == @backingInt(std.zig.Server.Message.PathPrefix.global_cache));714 comptime assert(3 == @backingInt(std.zig.Server.Message.PathPrefix.global_cache));
742 comptime assert(4 == @backingInt(std.zig.Server.Message.PathPrefix.build_root));715 comptime assert(4 == @backingInt(std.zig.Server.Message.PathPrefix.build_root));
743 comptime assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == 5);716 comptime assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == 5);
744 const gpa = man.cache.gpa;717 try man.addDiscoveredPath(.{
745 const prefixed_path: Cache.PrefixedPath = .{718 .discovered_path = .{ .prefixed = .{
746 .prefix = switch (p.root) {719 .prefix = switch (p.root) {
747 .none => {720 .none => {
748 const path = try p.toAbsolute(dirs, gpa);721 const gpa = man.cache.gpa;
749 defer gpa.free(path);722 const path = try p.toAbsolute(dirs, gpa);
750 return man.addFilePostContents(path, bytes, stat);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,
751 },734 },
752 .zig_lib => 1,735 .sub_path = p.sub_path,
753 .local_cache => 2,736 } },
754 .global_cache => 3,737 .contents = contents,
755 .build_root => 4,738 .stat = stat,
756 },739 });
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);
762 }740 }
763};741};
764742
...@@ -1341,19 +1319,19 @@ pub const cache_helpers = struct {...@@ -1341,19 +1319,19 @@ pub const cache_helpers = struct {
1341 }1319 }
1342 }1320 }
13431321
1344 pub fn hashCSource(self: *Cache.Manifest, c_source: CSourceFile) !void {1322 pub fn hashCSource(man: *Cache.Manifest, c_source: CSourceFile) !void {
1345 _ = try self.addFilePath(.initCwd(c_source.src_path), null);1323 _ = try man.addInputPath(.initCwd(c_source.src_path), .{});
1346 // Hash the extra flags, with special care to call addFile for file parameters.1324 // Hash the extra flags, with special care to call addFile for file parameters.
1347 // TODO this logic can likely be improved by utilizing clang_options_data.zig.1325 // TODO this logic can likely be improved by utilizing clang_options_data.zig.
1348 const file_args = [_][]const u8{"-include"};1326 const file_args = [_][]const u8{"-include"};
1349 var arg_i: usize = 0;1327 var arg_i: usize = 0;
1350 while (arg_i < c_source.extra_flags.len) : (arg_i += 1) {1328 while (arg_i < c_source.extra_flags.len) : (arg_i += 1) {
1351 const arg = c_source.extra_flags[arg_i];1329 const arg = c_source.extra_flags[arg_i];
1352 self.hash.addBytes(arg);1330 man.hash.addBytes(arg);
1353 for (file_args) |file_arg| {1331 for (file_args) |file_arg| {
1354 if (mem.eql(u8, file_arg, arg) and arg_i + 1 < c_source.extra_flags.len) {1332 if (mem.eql(u8, file_arg, arg) and arg_i + 1 < c_source.extra_flags.len) {
1355 arg_i += 1;1333 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]), .{});
1357 }1335 }
1358 }1336 }
1359 }1337 }
...@@ -2835,41 +2813,25 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE...@@ -2835,41 +2813,25 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
2835 man.want_shared_lock = false;2813 man.want_shared_lock = false;
2836 }2814 }
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) {
2839 error.Canceled, error.OutOfMemory => |e| return e,2818 error.Canceled, error.OutOfMemory => |e| return e,
2840 error.CacheCheckFailed => switch (man.diagnostic) {2819 error.CacheCheckFailed => {
2841 .none => unreachable,2820 return comp.setMiscFailure(.check_whole_cache, "checking cache failed: {f}", .{diag.fmt(&man)});
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 },
2854 },2821 },
2855 error.InvalidFormat => return comp.setMiscFailure(
2856 .check_whole_cache,
2857 "failed to check cache: invalid manifest file format",
2858 .{},
2859 ),
2860 };2822 };
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) {
2862 // In this case the cache hit contains the full set of file system inputs. Nice!2825 // In this case the cache hit contains the full set of file system inputs. Nice!
2863 if (comp.file_system_inputs) |buf| try man.populateFileSystemInputs(buf);2826 if (comp.file_system_inputs) |buf| try man.populateFileSystemInputs(buf);
2864 if (comp.parent_whole_cache) |pwc| {2827 if (comp.parent_whole_cache) |pwc| {
2865 try pwc.mutex.lock(io);2828 try pwc.mutex.lock(io);
2866 defer pwc.mutex.unlock(io);2829 defer pwc.mutex.unlock(io);
2867 try man.populateOtherManifest(pwc.manifest, pwc.prefix_map);2830 try pwc.manifest.addDiscoveredManifest(&man, pwc.prefix_map);
2868 }2831 }
28692832
2870 comp.last_update_was_cache_hit = true;2833 comp.last_update_was_cache_hit = true;
2871 log.debug("CacheMode.whole cache hit for {s}", .{comp.root_name});2834 const bin_digest = man.hitDigest();
2872 const bin_digest = man.finalBin();
28732835
2874 comp.digest = bin_digest;2836 comp.digest = bin_digest;
28752837
...@@ -2877,10 +2839,9 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE...@@ -2877,10 +2839,9 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
2877 whole.lock = man.toOwnedLock();2839 whole.lock = man.toOwnedLock();
2878 return;2840 return;
2879 }2841 }
2880 log.debug("CacheMode.whole cache miss for {s}", .{comp.root_name});
28812842
2882 if (ignore_hit) {2843 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.
2884 man.want_shared_lock = true;2845 man.want_shared_lock = true;
2885 }2846 }
28862847
...@@ -3036,7 +2997,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE...@@ -3036,7 +2997,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
3036 }2997 }
30372998
3038 if (build_options.enable_debug_extensions and comp.verbose_intern_pool) {2999 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});
3040 zcu.intern_pool.dump();3001 zcu.intern_pool.dump();
3041 }3002 }
30423003
...@@ -3048,11 +3009,8 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE...@@ -3048,11 +3009,8 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
30483009
3049 if (comp.link_depfile) |depfile_path| if (comp.bin_file) |lf| {3010 if (comp.link_depfile) |depfile_path| if (comp.bin_file) |lf| {
3050 assert(comp.file_system_inputs != null);3011 assert(comp.file_system_inputs != null);
3051 comp.createDepFile(depfile_path, lf.emit) catch |err| comp.setMiscFailure(3012 comp.createDepFile(depfile_path, lf.emit) catch |err|
3052 .link_depfile,3013 comp.setMiscFailure(.link_depfile, "writing linker dependency file failed: {t}", .{err});
3053 "unable to write linker dependency file: {t}",
3054 .{err},
3055 );
3056 };3014 };
30573015
3058 if (anyErrors(comp)) {3016 if (anyErrors(comp)) {
...@@ -3075,10 +3033,10 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE...@@ -3075,10 +3033,10 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
3075 if (comp.parent_whole_cache) |pwc| {3033 if (comp.parent_whole_cache) |pwc| {
3076 try pwc.mutex.lock(io);3034 try pwc.mutex.lock(io);
3077 defer pwc.mutex.unlock(io);3035 defer pwc.mutex.unlock(io);
3078 try man.populateOtherManifest(pwc.manifest, pwc.prefix_map);3036 try pwc.manifest.addDiscoveredManifest(&man, pwc.prefix_map);
3079 }3037 }
30803038
3081 const bin_digest = man.finalBin();3039 const bin_digest = man.missDigest();
3082 const hex_digest = Cache.binToHex(bin_digest);3040 const hex_digest = Cache.binToHex(bin_digest);
30833041
3084 // Work around windows `AccessDenied` if any files within this3042 // 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...@@ -3168,7 +3126,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
3168 }3126 }
31693127
3170 // Failure here only means an unnecessary cache miss.3128 // 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
3173 assert(whole.lock == null);3131 assert(whole.lock == null);
3174 whole.lock = man.toOwnedLock();3132 whole.lock = man.toOwnedLock();
...@@ -3410,7 +3368,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes...@@ -3410,7 +3368,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
3410 try link.hashInputs(man, comp.link_inputs);3368 try link.hashInputs(man, comp.link_inputs);
34113369
3412 for (comp.c_objects.items) |c_object| {3370 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), .{});
3414 man.hash.addOptional(c_object.src.ext);3372 man.hash.addOptional(c_object.src.ext);
3415 man.hash.addListOfBytes(c_object.src.extra_flags);3373 man.hash.addListOfBytes(c_object.src.extra_flags);
3416 }3374 }
...@@ -3418,11 +3376,11 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes...@@ -3418,11 +3376,11 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
3418 for (comp.win32_resources.items) |win32_resource| {3376 for (comp.win32_resources.items) |win32_resource| {
3419 switch (win32_resource.src) {3377 switch (win32_resource.src) {
3420 .rc => |rc_src| {3378 .rc => |rc_src| {
3421 _ = try man.addFilePath(.initCwd(rc_src.src_path), null);3379 _ = try man.addInputPath(.initCwd(rc_src.src_path), .{});
3422 man.hash.addListOfBytes(rc_src.extra_flags);3380 man.hash.addListOfBytes(rc_src.extra_flags);
3423 },3381 },
3424 .manifest => |manifest_path| {3382 .manifest => |manifest_path| {
3425 _ = try man.addFilePath(.initCwd(manifest_path), null);3383 _ = try man.addInputPath(.initCwd(manifest_path), .{});
3426 },3384 },
3427 }3385 }
3428 }3386 }
...@@ -3454,8 +3412,8 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes...@@ -3454,8 +3412,8 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
34543412
3455 const opts = comp.cache_use.whole.lf_open_opts;3413 const opts = comp.cache_use.whole.lf_open_opts;
34563414
3457 try man.addOptionalFilePath(opts.linker_script);3415 try man.addInputPathOptional(opts.linker_script, .{});
3458 try man.addOptionalFilePath(opts.version_script);3416 try man.addInputPathOptional(opts.version_script, .{});
3459 man.hash.add(opts.allow_undefined_version);3417 man.hash.add(opts.allow_undefined_version);
3460 man.hash.addOptional(opts.enable_new_dtags);3418 man.hash.addOptional(opts.enable_new_dtags);
34613419
...@@ -3514,7 +3472,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes...@@ -3514,7 +3472,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
35143472
3515 // Mach-O specific stuff3473 // Mach-O specific stuff
3516 try link.File.MachO.hashAddFrameworks(man, opts.frameworks);3474 try link.File.MachO.hashAddFrameworks(man, opts.frameworks);
3517 try man.addOptionalFilePath(opts.entitlements);3475 try man.addInputPathOptional(opts.entitlements, .{});
3518 man.hash.addOptional(opts.pagezero_size);3476 man.hash.addOptional(opts.pagezero_size);
3519 man.hash.addOptional(opts.headerpad_size);3477 man.hash.addOptional(opts.headerpad_size);
3520 man.hash.add(opts.headerpad_max_install_names);3478 man.hash.add(opts.headerpad_max_install_names);
...@@ -5123,9 +5081,13 @@ pub fn translateC(...@@ -5123,9 +5081,13 @@ pub fn translateC(
5123 try @import("main.zig").translateC(gpa, arena, io, argv.items, environ_map, prog_node, comp.thread_limit, &stdout);5081 try @import("main.zig").translateC(gpa, arena, io, argv.items, environ_map, prog_node, comp.thread_limit, &stdout);
51245082
5125 if (out_dep_path) |dep_file_path| add_deps: {5083 if (out_dep_path) |dep_file_path| add_deps: {
5084 var diagnostic: Cache.Manifest.AddDiscoveredDepFileDiagnostic = undefined;
5126 const dep_basename = fs.path.basename(dep_file_path);5085 const dep_basename = fs.path.basename(dep_file_path);
5127 // Add the files depended on to the cache system, if a dep file was emitted5086 // 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) {
5129 error.FileNotFound => break :add_deps,5091 error.FileNotFound => break :add_deps,
5130 else => |e| return e,5092 else => |e| return e,
5131 };5093 };
...@@ -5134,14 +5096,17 @@ pub fn translateC(...@@ -5134,14 +5096,17 @@ pub fn translateC(
5134 .whole => |whole| if (whole.cache_manifest) |whole_cache_manifest| {5096 .whole => |whole| if (whole.cache_manifest) |whole_cache_manifest| {
5135 try whole.cache_manifest_mutex.lock(io);5097 try whole.cache_manifest_mutex.lock(io);
5136 defer whole.cache_manifest_mutex.unlock(io);5098 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);
5138 },5103 },
5139 .incremental, .none => {},5104 .incremental, .none => {},
5140 }5105 }
51415106
5142 // Just to save disk space, we delete the file because it is never needed again.5107 // Just to save disk space, we delete the file because it is never needed again.
5143 cache_tmp_dir.deleteFile(io, dep_basename) catch |err| {5108 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 });
5145 };5110 };
5146 }5111 }
51475112
...@@ -5149,9 +5114,9 @@ pub fn translateC(...@@ -5149,9 +5114,9 @@ pub fn translateC(
5149 var reader: std.Io.Reader = .fixed(stdout);5114 var reader: std.Io.Reader = .fixed(stdout);
5150 const MessageHeader = std.zig.Server.Message.Header;5115 const MessageHeader = std.zig.Server.Message.Header;
5151 const header = reader.takeStruct(MessageHeader, .little) catch |err|5116 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});
5153 const body = reader.take(header.bytes_len) catch |err|5118 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 });
5155 switch (header.tag) {5120 switch (header.tag) {
5156 .error_bundle => {5121 .error_bundle => {
5157 const error_bundle = try std.zig.Server.allocErrorBundle(gpa, body);5122 const error_bundle = try std.zig.Server.allocErrorBundle(gpa, body);
...@@ -5161,11 +5126,11 @@ pub fn translateC(...@@ -5161,11 +5126,11 @@ pub fn translateC(
5161 .errors = error_bundle,5126 .errors = error_bundle,
5162 };5127 };
5163 },5128 },
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}),
5165 }5130 }
5166 }5131 }
51675132
5168 const bin_digest = man.finalBin();5133 const bin_digest = man.missDigest();
5169 const hex_digest = Cache.binToHex(bin_digest);5134 const hex_digest = Cache.binToHex(bin_digest);
5170 const o_sub_path = "o" ++ fs.path.sep_str ++ hex_digest;5135 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...@@ -5556,7 +5521,18 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
5556 const target = comp.getTarget();5521 const target = comp.getTarget();
5557 assert(target.ofmt != .c);5522 assert(target.ofmt != .c);
5558 const o_ext = target.ofmt.fileExt(target.cpu.arch);5523 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 }
5560 var argv: std.array_list.Managed([]const u8) = .init(gpa);5536 var argv: std.array_list.Managed([]const u8) = .init(gpa);
5561 defer argv.deinit();5537 defer argv.deinit();
55625538
...@@ -5683,11 +5659,11 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr...@@ -5683,11 +5659,11 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
5683 // Just to save disk space, we delete the files that are never needed again.5659 // Just to save disk space, we delete the files that are never needed again.
5684 defer if (out_diag_path) |diag_file_path| zig_cache_tmp_dir.deleteFile(io, fs.path.basename(diag_file_path)) catch |err| switch (err) {5660 defer if (out_diag_path) |diag_file_path| zig_cache_tmp_dir.deleteFile(io, fs.path.basename(diag_file_path)) catch |err| switch (err) {
5685 error.FileNotFound => {}, // the file wasn't created due to an error we reported5661 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 }),
5687 };5663 };
5688 defer if (out_dep_path) |dep_file_path| zig_cache_tmp_dir.deleteFile(io, fs.path.basename(dep_file_path)) catch |err| switch (err) {5664 defer if (out_dep_path) |dep_file_path| zig_cache_tmp_dir.deleteFile(io, fs.path.basename(dep_file_path)) catch |err| switch (err) {
5689 error.FileNotFound => {}, // the file wasn't created due to an error we reported5665 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 }),
5691 };5667 };
5692 if (std.process.can_spawn) {5668 if (std.process.can_spawn) {
5693 if (comp.clang_passthrough_mode) {5669 if (comp.clang_passthrough_mode) {
...@@ -5776,8 +5752,10 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr...@@ -5776,8 +5752,10 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
5776 const dep_basename = fs.path.basename(dep_file_path);5752 const dep_basename = fs.path.basename(dep_file_path);
57775753
5778 if (comp.file_system_inputs != null) {5754 if (comp.file_system_inputs != null) {
5755 // TODO instead of this rely on passing the manifest contents directly
5756
5779 // Use the same file size limit as the cache code does for dependency files.5757 // 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);
5781 defer gpa.free(dep_file_contents);5759 defer gpa.free(dep_file_contents);
57825760
5783 var str_buf: std.ArrayList(u8) = .empty;5761 var str_buf: std.ArrayList(u8) = .empty;
...@@ -5792,9 +5770,8 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr...@@ -5792,9 +5770,8 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
5792 try token.resolve(gpa, &str_buf);5770 try token.resolve(gpa, &str_buf);
5793 break :p try .fromUnresolved(arena, comp.dirs, &.{str_buf.items});5771 break :p try .fromUnresolved(arena, comp.dirs, &.{str_buf.items});
5794 },5772 },
5795 else => |err| {5773 else => |err_token| {
5796 try err.printError(gpa, &str_buf);5774 log.err("failed parsing {s}: {f}", .{ dep_basename, err_token });
5797 log.err("failed parsing {s}: {s}", .{ dep_basename, str_buf.items });
5798 return error.InvalidDepFile;5775 return error.InvalidDepFile;
5799 },5776 },
5800 };5777 };
...@@ -5803,13 +5780,26 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr...@@ -5803,13 +5780,26 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
5803 }5780 }
58045781
5805 // Add the files depended on to the cache system.5782 // 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);
5807 switch (comp.cache_use) {5791 switch (comp.cache_use) {
5808 .whole => |whole| {5792 .whole => |whole| {
5809 if (whole.cache_manifest) |whole_cache_manifest| {5793 if (whole.cache_manifest) |whole_cache_manifest| {
5810 try whole.cache_manifest_mutex.lock(io);5794 try whole.cache_manifest_mutex.lock(io);
5811 defer whole.cache_manifest_mutex.unlock(io);5795 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);
5813 }5803 }
5814 },5804 },
5815 .incremental, .none => {},5805 .incremental, .none => {},
...@@ -5817,16 +5807,28 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr...@@ -5817,16 +5807,28 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
5817 }5807 }
58185808
5819 // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.5809 // 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
5822 // Rename into place.5825 // Rename into place.
5823 const digest = man.final();5826 const o_sub_path = try fs.path.join(arena, &.{ "o", &digest });
5824 const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest });
5825 var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{});5827 var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{});
5826 defer o_dir.close(io);5828 defer o_dir.close(io);
5827 const tmp_basename = fs.path.basename(out_obj_path);5829 const tmp_basename = fs.path.basename(out_obj_path);
5828 try Io.Dir.rename(zig_cache_tmp_dir, tmp_basename, o_dir, o_basename, io);5830 try Io.Dir.rename(zig_cache_tmp_dir, tmp_basename, o_dir, o_basename, io);
5829 break :blk digest;5831 break :d digest;
5830 };5832 };
58315833
5832 if (man.have_exclusive_lock) {5834 if (man.have_exclusive_lock) {
...@@ -5834,11 +5836,8 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr...@@ -5834,11 +5836,8 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
5834 // possible we had a hit and the manifest is dirty, for example if the file mtime changed but5836 // possible we had a hit and the manifest is dirty, for example if the file mtime changed but
5835 // the contents were the same, we hit the cache but the manifest is dirty and we need to update5837 // the contents were the same, we hit the cache but the manifest is dirty and we need to update
5836 // it to prevent doing a full file content comparison the next time around.5838 // it to prevent doing a full file content comparison the next time around.
5837 man.writeManifest() catch |err| {5839 man.finalize() catch |err|
5838 log.warn("failed to write cache manifest when compiling '{s}': {s}", .{5840 log.warn("failed to write cache manifest when compiling {q}: {t}", .{ c_object.src.src_path, err });
5839 c_object.src.src_path, @errorName(err),
5840 });
5841 };
5842 }5841 }
58435842
5844 const o_basename = try std.fmt.allocPrint(arena, "{s}{s}", .{ o_basename_noext, o_ext });5843 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...@@ -5900,15 +5899,23 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
5900 // the XML data as a RT_MANIFEST resource. This means we can skip preprocessing,5899 // the XML data as a RT_MANIFEST resource. This means we can skip preprocessing,
5901 // include paths, CLI options, etc.5900 // include paths, CLI options, etc.
5902 if (win32_resource.src == .manifest) {5901 if (win32_resource.src == .manifest) {
5903 _ = try man.addFilePath(.initCwd(src_path), null);5902 _ = try man.addInputPath(.initCwd(src_path), .{});
59045903
5905 const rc_basename = try std.fmt.allocPrint(arena, "{s}.rc", .{src_basename});5904 const rc_basename = try std.fmt.allocPrint(arena, "{s}.rc", .{src_basename});
5906 const res_basename = try std.fmt.allocPrint(arena, "{s}.res", .{src_basename});5905 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: {
5909 // The digest only depends on the .manifest file, so we can5916 // The digest only depends on the .manifest file, so we can
5910 // get the digest now and write the .res directly to the cache5917 // get the digest now and write the .res directly to the cache
5911 const digest = man.final();5918 const digest = man.missDigestHex();
59125919
5913 const o_sub_path = try fs.path.join(arena, &.{ "o", &digest });5920 const o_sub_path = try fs.path.join(arena, &.{ "o", &digest });
5914 var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{});5921 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...@@ -5970,13 +5977,12 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
59705977
5971 try spawnZigRc(comp, win32_resource, arena, argv.items, child_progress_node);5978 try spawnZigRc(comp, win32_resource, arena, argv.items, child_progress_node);
59725979
5973 break :blk digest;5980 break :miss digest;
5974 };5981 };
59755982
5976 if (man.have_exclusive_lock) {5983 if (man.have_exclusive_lock) {
5977 man.writeManifest() catch |err| {5984 man.finalize() catch |err|
5978 log.warn("failed to write cache manifest when compiling '{s}': {s}", .{ src_path, @errorName(err) });5985 log.warn("failed to write cache manifest when compiling {q}: {t}", .{ src_path, err });
5979 };
5980 }5986 }
59815987
5982 win32_resource.status = .{5988 win32_resource.status = .{
...@@ -5993,12 +5999,20 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32...@@ -5993,12 +5999,20 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
5993 // We now know that we're compiling an .rc file5999 // We now know that we're compiling an .rc file
5994 const rc_src = win32_resource.src.rc;6000 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), .{});
5997 man.hash.addListOfBytes(rc_src.extra_flags);6003 man.hash.addListOfBytes(rc_src.extra_flags);
59986004
5999 const rc_basename_noext = src_basename[0 .. src_basename.len - fs.path.extension(src_basename).len];6005 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: {
6002 var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, "tmp", .{});6016 var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, "tmp", .{});
6003 defer zig_cache_tmp_dir.close(io);6017 defer zig_cache_tmp_dir.close(io);
60046018
...@@ -6055,12 +6069,14 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32...@@ -6055,12 +6069,14 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
6055 return comp.failWin32Resource(win32_resource, "depfile from zig rc has unexpected format", .{});6069 return comp.failWin32Resource(win32_resource, "depfile from zig rc has unexpected format", .{});
6056 }6070 }
6057 const dep_file_path = element.string;6071 const dep_file_path = element.string;
6058 try man.addFilePost(dep_file_path);6072 try man.addDiscoveredPath(.{ .discovered_path = .{ .unresolved = .initCwd(dep_file_path) } });
6059 switch (comp.cache_use) {6073 switch (comp.cache_use) {
6060 .whole => |whole| if (whole.cache_manifest) |whole_cache_manifest| {6074 .whole => |whole| if (whole.cache_manifest) |whole_cache_manifest| {
6061 try whole.cache_manifest_mutex.lock(io);6075 try whole.cache_manifest_mutex.lock(io);
6062 defer whole.cache_manifest_mutex.unlock(io);6076 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 });
6064 },6080 },
6065 .incremental, .none => {},6081 .incremental, .none => {},
6066 }6082 }
...@@ -6068,13 +6084,13 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32...@@ -6068,13 +6084,13 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
6068 }6084 }
60696085
6070 // Rename into place.6086 // Rename into place.
6071 const digest = man.final();6087 const digest = man.missDigestHex();
6072 const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest });6088 const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest });
6073 var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{});6089 var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{});
6074 defer o_dir.close(io);6090 defer o_dir.close(io);
6075 const tmp_basename = fs.path.basename(out_res_path);6091 const tmp_basename = fs.path.basename(out_res_path);
6076 try Io.Dir.rename(zig_cache_tmp_dir, tmp_basename, o_dir, res_filename, io);6092 try Io.Dir.rename(zig_cache_tmp_dir, tmp_basename, o_dir, res_filename, io);
6077 break :blk digest;6093 break :miss digest;
6078 };6094 };
60796095
6080 if (man.have_exclusive_lock) {6096 if (man.have_exclusive_lock) {
...@@ -6082,8 +6098,8 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32...@@ -6082,8 +6098,8 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
6082 // possible we had a hit and the manifest is dirty, for example if the file mtime changed but6098 // possible we had a hit and the manifest is dirty, for example if the file mtime changed but
6083 // the contents were the same, we hit the cache but the manifest is dirty and we need to update6099 // the contents were the same, we hit the cache but the manifest is dirty and we need to update
6084 // it to prevent doing a full file content comparison the next time around.6100 // it to prevent doing a full file content comparison the next time around.
6085 man.writeManifest() catch |err| {6101 man.finalize() catch |err| {
6086 log.warn("failed to write cache manifest when compiling '{s}': {s}", .{ rc_src.src_path, @errorName(err) });6102 log.warn("failed to write cache manifest when compiling {q}: {t}", .{ rc_src.src_path, err });
6087 };6103 };
6088 }6104 }
60896105
src/Sema.zig+4-8
...@@ -12974,7 +12974,7 @@ fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A...@@ -12974,7 +12974,7 @@ fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
1297412974
12975 const result = ef_idx.get(zcu);12975 const result = ef_idx.get(zcu);
12976 if (result.val == .none) {12976 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.? });
12978 }12978 }
1297912979
12980 return Air.internedToRef(result.val);12980 return Air.internedToRef(result.val);
...@@ -15911,9 +15911,7 @@ fn zirClosureGet(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat...@@ -15911,9 +15911,7 @@ fn zirClosureGet(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
15911 const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu).?;15911 const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu).?;
15912 const tree = file.getTree(zcu) catch |err| {15912 const tree = file.getTree(zcu) catch |err| {
15913 // In this case we emit a warning + a less precise source location.15913 // In this case we emit a warning + a less precise source location.
15914 log.warn("unable to load {f}: {s}", .{15914 log.warn("failed loading {qf}: {t}", .{ file.path.fmt(zcu.comp), err });
15915 file.path.fmt(zcu.comp), @errorName(err),
15916 });
15917 break :name null;15915 break :name null;
15918 };15916 };
15919 const node = src_node.toAbsolute(src_base_node);15917 const node = src_node.toAbsolute(src_base_node);
...@@ -15939,9 +15937,7 @@ fn zirClosureGet(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat...@@ -15939,9 +15937,7 @@ fn zirClosureGet(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
15939 const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu).?;15937 const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu).?;
15940 const tree = file.getTree(zcu) catch |err| {15938 const tree = file.getTree(zcu) catch |err| {
15941 // In this case we emit a warning + a less precise source location.15939 // In this case we emit a warning + a less precise source location.
15942 log.warn("unable to load {f}: {s}", .{15940 log.warn("failed loading {qf}: {t}", .{ file.path.fmt(zcu.comp), err });
15943 file.path.fmt(zcu.comp), @errorName(err),
15944 });
15945 break :name null;15941 break :name null;
15946 };15942 };
15947 const node = src_node.toAbsolute(src_base_node);15943 const node = src_node.toAbsolute(src_base_node);
...@@ -15950,7 +15946,7 @@ fn zirClosureGet(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat...@@ -15950,7 +15946,7 @@ fn zirClosureGet(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
15950 };15946 };
1595115947
15952 const msg = if (name) |some|15948 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})
15954 else15950 else
15955 try sema.errMsg(src, "variable not accessible from inner function", .{});15951 try sema.errMsg(src, "variable not accessible from inner function", .{});
15956 errdefer msg.destroy(sema.gpa);15952 errdefer msg.destroy(sema.gpa);
src/Zcu.zig+2-2
...@@ -974,7 +974,7 @@ pub const File = struct {...@@ -974,7 +974,7 @@ pub const File = struct {
974 success,974 success,
975 },975 },
976 /// Whether this is populated depends on `status`.976 /// Whether this is populated depends on `status`.
977 stat: Cache.File.Stat,977 stat: Cache.Manifest.Stat,
978978
979 /// Whether this file is the generated file of a "builtin" module. This matters because those979 /// Whether this file is the generated file of a "builtin" module. This matters because those
980 /// files are generated and stored in-nemory rather than being read off-disk. The rest of the980 /// 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 {...@@ -1246,7 +1246,7 @@ pub const EmbedFile = struct {
1246 val: InternPool.Index,1246 val: InternPool.Index,
1247 /// If this is `null` and `val` is `.none`, the file has never been loaded.1247 /// If this is `null` and `val` is `.none`, the file has never been loaded.
1248 err: ?(Io.File.OpenError || Io.File.StatError || Io.File.Reader.Error || error{UnexpectedEof}),1248 err: ?(Io.File.OpenError || Io.File.StatError || Io.File.Reader.Error || error{UnexpectedEof}),
1249 stat: Cache.File.Stat,1249 stat: Cache.Manifest.Stat,
12501250
1251 pub const Index = enum(u32) {1251 pub const Index = enum(u32) {
1252 _,1252 _,
src/Zcu/PerThread.zig+22-27
...@@ -224,11 +224,12 @@ pub fn update(...@@ -224,11 +224,12 @@ pub fn update(
224 const result = res: {224 const result = res: {
225 try whole.cache_manifest_mutex.lock(io);225 try whole.cache_manifest_mutex.lock(io);
226 defer whole.cache_manifest_mutex.unlock(io);226 defer whole.cache_manifest_mutex.unlock(io);
227 if (file.source) |source| {227 break :res file.path.addToCacheManifestAsDiscovered(
228 break :res file.path.addToCacheManifestPostHitContents(man, &comp.dirs, source, file.stat);228 man,
229 } else {229 &comp.dirs,
230 break :res file.path.addToCacheManifestPostHit(man, &comp.dirs);230 file.source,
231 }231 if (file.source != null) file.stat else null,
232 );
232 };233 };
233 result catch |err| switch (err) {234 result catch |err| switch (err) {
234 error.OutOfMemory => |e| return e,235 error.OutOfMemory => |e| return e,
...@@ -348,11 +349,8 @@ pub fn update(...@@ -348,11 +349,8 @@ pub fn update(
348 }349 }
349}350}
350fn workerUpdateBuiltinFile(comp: *Compilation, file: *Zcu.File) void {351fn workerUpdateBuiltinFile(comp: *Compilation, file: *Zcu.File) void {
351 Builtin.updateFileOnDisk(file, comp) catch |err| comp.lockAndSetMiscFailure(352 Builtin.updateFileOnDisk(file, comp) catch |err|
352 .write_builtin_zig,353 comp.lockAndSetMiscFailure(.write_builtin_zig, "unable to write {qf}: {t}", .{ file.path.fmt(comp), err });
353 "unable to write '{f}': {s}",
354 .{ file.path.fmt(comp), @errorName(err) },
355 );
356}354}
357fn workerUpdateFile(355fn workerUpdateFile(
358 comp: *Compilation,356 comp: *Compilation,
...@@ -371,7 +369,9 @@ fn workerUpdateFile(...@@ -371,7 +369,9 @@ fn workerUpdateFile(
371 const active = comp.zcu.?.activate(tid);369 const active = comp.zcu.?.activate(tid);
372 defer active.deactivate();370 defer active.deactivate();
373 active.pt.updateFile(file_index, file) catch |err| {371 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) {
375 error.OutOfMemory => {375 error.OutOfMemory => {
376 comp.mutex.lockUncancelable(io);376 comp.mutex.lockUncancelable(io);
377 defer comp.mutex.unlock(io);377 defer comp.mutex.unlock(io);
...@@ -2836,26 +2836,21 @@ fn updateBuiltinModule(pt: Zcu.PerThread, opts: Builtin) Allocator.Error!struct...@@ -2836,26 +2836,21 @@ fn updateBuiltinModule(pt: Zcu.PerThread, opts: Builtin) Allocator.Error!struct
2836 assert(mem.eql(u8, import_path, "std")); // the single import is of 'std'2836 assert(mem.eql(u8, import_path, "std")); // the single import is of 'std'
2837 }2837 }
28382838
2839 Builtin.updateFileOnDisk(file, comp) catch |err| comp.setMiscFailure(2839 Builtin.updateFileOnDisk(file, comp) catch |err|
2840 .write_builtin_zig,2840 comp.setMiscFailure(.write_builtin_zig, "unable to write {qf}: {t}", .{ file.path.fmt(comp), err });
2841 "unable to write '{f}': {s}",
2842 .{ file.path.fmt(comp), @errorName(err) },
2843 );
2844 return .{2841 return .{
2845 .file = file_index,2842 .file = file_index,
2846 .module_root = mod,2843 .module_root = mod,
2847 };2844 };
2848}2845}
28492846
2847pub const EmbedFileError = error{ImportOutsideModulePath} || Allocator.Error || Io.Cancelable;
2848
2850pub fn embedFile(2849pub fn embedFile(
2851 pt: Zcu.PerThread,2850 pt: Zcu.PerThread,
2852 cur_file: *Zcu.File,2851 cur_file: *Zcu.File,
2853 import_string: []const u8,2852 import_string: []const u8,
2854) error{2853) EmbedFileError!Zcu.EmbedFile.Index {
2855 OutOfMemory,
2856 Canceled,
2857 ImportOutsideModulePath,
2858}!Zcu.EmbedFile.Index {
2859 const zcu = pt.zcu;2854 const zcu = pt.zcu;
2860 const gpa = zcu.gpa;2855 const gpa = zcu.gpa;
28612856
...@@ -2938,7 +2933,7 @@ fn updateEmbedFileInner(...@@ -2938,7 +2933,7 @@ fn updateEmbedFileInner(
2938 };2933 };
2939 defer file.close(io);2934 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
2943 if (ef.val != .none) {2938 if (ef.val != .none) {
2944 const old_stat = ef.stat;2939 const old_stat = ef.stat;
...@@ -2995,10 +2990,7 @@ fn updateEmbedFileInner(...@@ -2995,10 +2990,7 @@ fn updateEmbedFileInner(
2995}2990}
29962991
2997/// Assumes that `path` is allocated into `gpa`. Takes ownership of `path` on success.2992/// Assumes that `path` is allocated into `gpa`. Takes ownership of `path` on success.
2998fn newEmbedFile(2993fn newEmbedFile(pt: Zcu.PerThread, path: Compilation.Path) EmbedFileError!*Zcu.EmbedFile {
2999 pt: Zcu.PerThread,
3000 path: Compilation.Path,
3001) !*Zcu.EmbedFile {
3002 const zcu = pt.zcu;2994 const zcu = pt.zcu;
3003 const comp = zcu.comp;2995 const comp = zcu.comp;
3004 const io = comp.io;2996 const io = comp.io;
...@@ -3035,7 +3027,10 @@ fn newEmbedFile(...@@ -3035,7 +3027,10 @@ fn newEmbedFile(
3035 try whole.cache_manifest_mutex.lock(io);3027 try whole.cache_manifest_mutex.lock(io);
3036 defer whole.cache_manifest_mutex.unlock(io);3028 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 };
3039 }3034 }
30403035
3041 return new_file;3036 return new_file;
src/libs/freebsd.zig+22-10
...@@ -458,14 +458,28 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -458,14 +458,28 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
458 man.hash.add(target.abi);458 man.hash.add(target.abi);
459 man.hash.add(target_os_version);459 man.hash.add(target_os_version);
460460
461 const abilists_index = try man.addFilePath(.{461 const abilists_index = try man.addInputPath(.{
462 .root_dir = comp.dirs.zig_lib,462 .root_dir = comp.dirs.zig_lib,
463 .sub_path = abilists_path,463 .sub_path = abilists_path,
464 }, abilists_max_size);464 }, .{
465465 .request_contents = true,
466 if (try man.hit(prog_node)) {466 });
467 const digest = man.final();
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();
469 return queueSharedObjects(comp, .{483 return queueSharedObjects(comp, .{
470 .lock = man.toOwnedLock(),484 .lock = man.toOwnedLock(),
471 .dir_path = .{485 .dir_path = .{
...@@ -475,7 +489,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -475,7 +489,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
475 });489 });
476 }490 }
477491
478 const digest = man.final();492 const digest = man.missDigestHex();
479 const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });493 const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });
480494
481 var o_directory: Cache.Directory = .{495 var o_directory: Cache.Directory = .{
...@@ -484,7 +498,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -484,7 +498,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
484 };498 };
485 defer o_directory.handle.close(io);499 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);
488 const metadata = try loadMetaData(gpa, abilists_contents);502 const metadata = try loadMetaData(gpa, abilists_contents);
489 defer metadata.destroy(gpa);503 defer metadata.destroy(gpa);
490504
...@@ -980,9 +994,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -980,9 +994,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
980 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);994 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);
981 }995 }
982996
983 man.writeManifest() catch |err| {997 man.finalize() catch |err| log.warn("failed to write cache manifest for FreeBSD libc stubs: {t}", .{err});
984 log.warn("failed to write cache manifest for FreeBSD libc stubs: {s}", .{@errorName(err)});
985 };
986998
987 return queueSharedObjects(comp, .{999 return queueSharedObjects(comp, .{
988 .lock = man.toOwnedLock(),1000 .lock = man.toOwnedLock(),
src/libs/glibc.zig+23-11
...@@ -698,14 +698,28 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -698,14 +698,28 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
698 man.hash.add(target.abi);698 man.hash.add(target.abi);
699 man.hash.add(target_version);699 man.hash.add(target_version);
700700
701 const abilists_index = try man.addFilePath(.{701 const abilists_index = try man.addInputPath(.{
702 .root_dir = comp.dirs.zig_lib,702 .root_dir = comp.dirs.zig_lib,
703 .sub_path = abilists_path,703 .sub_path = abilists_path,
704 }, abilists_max_size);704 }, .{
705705 .request_contents = true,
706 if (try man.hit(prog_node)) {706 });
707 const digest = man.final();
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();
709 return queueSharedObjects(comp, .{723 return queueSharedObjects(comp, .{
710 .lock = man.toOwnedLock(),724 .lock = man.toOwnedLock(),
711 .dir_path = .{725 .dir_path = .{
...@@ -715,8 +729,8 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -715,8 +729,8 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
715 });729 });
716 }730 }
717731
718 const digest = man.final();732 const digest = man.missDigestHex();
719 const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });733 const o_sub_path = try path.join(arena, &.{ "o", &digest });
720734
721 var o_directory: Cache.Directory = .{735 var o_directory: Cache.Directory = .{
722 .handle = try comp.dirs.global_cache.handle.createDirPathOpen(io, o_sub_path, .{}),736 .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...@@ -724,7 +738,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
724 };738 };
725 defer o_directory.handle.close(io);739 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);
728 const metadata = try loadMetaData(gpa, abilists_contents);742 const metadata = try loadMetaData(gpa, abilists_contents);
729 defer metadata.destroy(gpa);743 defer metadata.destroy(gpa);
730744
...@@ -1129,9 +1143,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -1129,9 +1143,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
1129 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);1143 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);
1130 }1144 }
11311145
1132 man.writeManifest() catch |err| {1146 man.finalize() catch |err| log.warn("failed to write cache manifest for glibc stubs: {t}", .{err});
1133 log.warn("failed to write cache manifest for glibc stubs: {s}", .{@errorName(err)});
1134 };
11351147
1136 return queueSharedObjects(comp, .{1148 return queueSharedObjects(comp, .{
1137 .lock = man.toOwnedLock(),1149 .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...@@ -246,35 +246,24 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8, prog_node: std.P
246 var man = cache.obtain();246 var man = cache.obtain();
247 defer man.deinit();247 defer man.deinit();
248248
249 _ = try man.addFilePath(def_file_path, null);249 _ = try man.addInputPath(def_file_path, .{});
250250
251 const final_lib_basename = try std.fmt.allocPrint(gpa, "{s}.lib", .{lib_name});251 const final_lib_basename = try std.fmt.allocPrint(gpa, "{s}.lib", .{lib_name});
252 errdefer gpa.free(final_lib_basename);252 errdefer gpa.free(final_lib_basename);
253253
254 const is_hit = man.hit(prog_node) catch |err| switch (err) {254 var diag: Cache.Manifest.CheckDiagnostic = undefined;
255 error.CacheCheckFailed => switch (man.diagnostic) {255 const status = man.check(&diag, prog_node) catch |err| switch (err) {
256 .none => unreachable,256 error.CacheCheckFailed => {
257 .manifest_create, .manifest_read, .manifest_lock => |e| {257 comp.lockAndSetMiscFailure(.windows_import_lib, "{s} cache check failed: {f}", .{
258 comp.setMiscFailure(.windows_import_lib, "checking cache failed: {t} {t}", .{ man.diagnostic, e });258 final_lib_basename, diag.fmt(&man),
259 return error.AlreadyReported;259 });
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", .{});
273 return error.AlreadyReported;260 return error.AlreadyReported;
274 },261 },
262 error.OutOfMemory, error.Canceled => |e| return e,
275 };263 };
276 if (is_hit) {264 log.debug("{s} cache {f}", .{ final_lib_basename, status.fmt(&man) });
277 const digest = man.final();265 if (status == .hit) {
266 const digest = man.hitDigestHex();
278 const sub_path = try std.fs.path.join(gpa, &.{ "o", &digest, final_lib_basename });267 const sub_path = try std.fs.path.join(gpa, &.{ "o", &digest, final_lib_basename });
279 errdefer gpa.free(sub_path);268 errdefer gpa.free(sub_path);
280269
...@@ -293,7 +282,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8, prog_node: std.P...@@ -293,7 +282,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8, prog_node: std.P
293 return crt_file_path;282 return crt_file_path;
294 }283 }
295284
296 const digest = man.final();285 const digest = man.missDigestHex();
297 const o_sub_path = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest });286 const o_sub_path = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest });
298 var o_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, o_sub_path, .{});287 var o_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, o_sub_path, .{});
299 defer o_dir.close(io);288 defer o_dir.close(io);
...@@ -365,9 +354,8 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8, prog_node: std.P...@@ -365,9 +354,8 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8, prog_node: std.P
365 try file_writer.interface.flush();354 try file_writer.interface.flush();
366 }355 }
367356
368 man.writeManifest() catch |err| {357 man.finalize() catch |err|
369 log.warn("failed to write cache manifest for DLL import {s}.lib: {s}", .{ lib_name, @errorName(err) });358 log.warn("failed to write cache manifest for DLL import {s}.lib: {t}", .{ lib_name, err });
370 };
371359
372 comp.mutex.lockUncancelable(io);360 comp.mutex.lockUncancelable(io);
373 defer comp.mutex.unlock(io);361 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...@@ -406,14 +406,28 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
406 man.hash.add(target.abi);406 man.hash.add(target.abi);
407 man.hash.add(target_version);407 man.hash.add(target_version);
408408
409 const abilists_index = try man.addFilePath(.{409 const abilists_index = try man.addInputPath(.{
410 .root_dir = comp.dirs.zig_lib,410 .root_dir = comp.dirs.zig_lib,
411 .sub_path = abilists_path,411 .sub_path = abilists_path,
412 }, abilists_max_size);412 }, .{
413413 .request_contents = true,
414 if (try man.hit(prog_node)) {414 });
415 const digest = man.final();
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();
417 return queueSharedObjects(comp, .{431 return queueSharedObjects(comp, .{
418 .lock = man.toOwnedLock(),432 .lock = man.toOwnedLock(),
419 .dir_path = .{433 .dir_path = .{
...@@ -423,8 +437,8 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -423,8 +437,8 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
423 });437 });
424 }438 }
425439
426 const digest = man.final();440 const digest = man.missDigestHex();
427 const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });441 const o_sub_path = try path.join(arena, &.{ "o", &digest });
428442
429 var o_directory: Cache.Directory = .{443 var o_directory: Cache.Directory = .{
430 .handle = try comp.dirs.global_cache.handle.createDirPathOpen(io, o_sub_path, .{}),444 .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...@@ -432,7 +446,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
432 };446 };
433 defer o_directory.handle.close(io);447 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);
436 const metadata = try loadMetaData(gpa, abilists_contents);450 const metadata = try loadMetaData(gpa, abilists_contents);
437 defer metadata.destroy(gpa);451 defer metadata.destroy(gpa);
438452
...@@ -641,9 +655,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -641,9 +655,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
641 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);655 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);
642 }656 }
643657
644 man.writeManifest() catch |err| {658 man.finalize() catch |err| log.warn("failed to write cache manifest for NetBSD libc stubs: {t}", .{err});
645 log.warn("failed to write cache manifest for NetBSD libc stubs: {s}", .{@errorName(err)});
646 };
647659
648 return queueSharedObjects(comp, .{660 return queueSharedObjects(comp, .{
649 .lock = man.toOwnedLock(),661 .lock = man.toOwnedLock(),
src/libs/openbsd.zig+22-10
...@@ -327,14 +327,28 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -327,14 +327,28 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
327 man.hash.add(target.abi);327 man.hash.add(target.abi);
328 man.hash.add(target_version);328 man.hash.add(target_version);
329329
330 const abilists_index = try man.addFilePath(.{330 const abilists_index = try man.addInputPath(.{
331 .root_dir = comp.dirs.zig_lib,331 .root_dir = comp.dirs.zig_lib,
332 .sub_path = abilists_path,332 .sub_path = abilists_path,
333 }, abilists_max_size);333 }, .{
334334 .request_contents = true,
335 if (try man.hit(prog_node)) {335 });
336 const digest = man.final();
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();
338 return queueSharedObjects(comp, .{352 return queueSharedObjects(comp, .{
339 .lock = man.toOwnedLock(),353 .lock = man.toOwnedLock(),
340 .dir_path = .{354 .dir_path = .{
...@@ -344,7 +358,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -344,7 +358,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
344 });358 });
345 }359 }
346360
347 const digest = man.final();361 const digest = man.missDigestHex();
348 const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });362 const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });
349363
350 var o_directory: Cache.Directory = .{364 var o_directory: Cache.Directory = .{
...@@ -353,7 +367,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -353,7 +367,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
353 };367 };
354 defer o_directory.handle.close(io);368 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);
357 const metadata = try loadMetaData(gpa, abilists_contents);371 const metadata = try loadMetaData(gpa, abilists_contents);
358 defer metadata.destroy(gpa);372 defer metadata.destroy(gpa);
359373
...@@ -562,9 +576,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -562,9 +576,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
562 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);576 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);
563 }577 }
564578
565 man.writeManifest() catch |err| {579 man.finalize() catch |err| log.warn("failed to write cache manifest for OpenBSD libc stubs: {t}", .{err});
566 log.warn("failed to write cache manifest for OpenBSD libc stubs: {s}", .{@errorName(err)});
567 };
568580
569 return queueSharedObjects(comp, .{581 return queueSharedObjects(comp, .{
570 .lock = man.toOwnedLock(),582 .lock = man.toOwnedLock(),
src/link.zig+17-8
...@@ -1500,11 +1500,11 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {...@@ -1500,11 +1500,11 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {
1500 base.loadInput(input) catch |err| switch (err) {1500 base.loadInput(input) catch |err| switch (err) {
1501 error.AlreadyReported => return, // error reported via diags1501 error.AlreadyReported => return, // error reported via diags
1502 else => |e| switch (input) {1502 else => |e| switch (input) {
1503 .dso => |dso| diags.addParseError(dso.path, "failed to parse shared library: {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: {s}", .{@errorName(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: {s}", .{@errorName(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: {s}", .{@errorName(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: {s}", .{@errorName(e)}),1507 .dso_exact => diags.addError("failed to handle dso_exact: {t}", .{e}),
1508 },1508 },
1509 };1509 };
1510 prog_node.completeOne();1510 prog_node.completeOne();
...@@ -1925,15 +1925,24 @@ pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void {...@@ -1925,15 +1925,24 @@ pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void {
1925 man.hash.add(@as(@typeInfo(Input).@"union".tag_type.?, link_input));1925 man.hash.add(@as(@typeInfo(Input).@"union".tag_type.?, link_input));
1926 switch (link_input) {1926 switch (link_input) {
1927 .object, .archive => |obj| {1927 .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 });
1929 man.hash.add(obj.must_link);1932 man.hash.add(obj.must_link);
1930 man.hash.add(obj.hidden);1933 man.hash.add(obj.hidden);
1931 },1934 },
1932 .res => |res| {1935 .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 });
1934 },1940 },
1935 .dso => |dso| {1941 .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 });
1937 man.hash.add(dso.needed);1946 man.hash.add(dso.needed);
1938 man.hash.add(dso.weak);1947 man.hash.add(dso.weak);
1939 man.hash.add(dso.reexport);1948 man.hash.add(dso.reexport);
src/link/Elf.zig+2-2
...@@ -1120,7 +1120,7 @@ fn parseDso(...@@ -1120,7 +1120,7 @@ fn parseDso(
11201120
1121 const handle = dso.file;1121 const handle = dso.file;
11221122
1123 const stat = Stat.fromFs(try handle.stat(io));1123 const stat: Stat = .init(try handle.stat(io));
1124 var header = try SharedObject.parseHeader(gpa, io, diags, dso.path, handle, stat, target);1124 var header = try SharedObject.parseHeader(gpa, io, diags, dso.path, handle, stat, target);
1125 defer header.deinit(gpa);1125 defer header.deinit(gpa);
11261126
...@@ -4407,7 +4407,7 @@ const mem = std.mem;...@@ -4407,7 +4407,7 @@ const mem = std.mem;
4407const Allocator = std.mem.Allocator;4407const Allocator = std.mem.Allocator;
4408const Hash = std.hash.Wyhash;4408const Hash = std.hash.Wyhash;
4409const Path = std.Build.Cache.Path;4409const Path = std.Build.Cache.Path;
4410const Stat = std.Build.Cache.File.Stat;4410const Stat = std.Build.Cache.Manifest.Stat;
44114411
4412const codegen = @import("../codegen.zig");4412const codegen = @import("../codegen.zig");
4413const eh_frame = @import("Elf/eh_frame.zig");4413const eh_frame = @import("Elf/eh_frame.zig");
src/link/Elf/SharedObject.zig+1-1
...@@ -7,7 +7,7 @@ const elf = std.elf;...@@ -7,7 +7,7 @@ const elf = std.elf;
7const log = std.log.scoped(.elf);7const log = std.log.scoped(.elf);
8const mem = std.mem;8const mem = std.mem;
9const Path = std.Build.Cache.Path;9const Path = std.Build.Cache.Path;
10const Stat = std.Build.Cache.File.Stat;10const Stat = std.Build.Cache.Manifest.Stat;
11const Allocator = mem.Allocator;11const Allocator = mem.Allocator;
1212
13const Elf = @import("../Elf.zig");13const Elf = @import("../Elf.zig");
src/link/MachO.zig+1-1
...@@ -155,7 +155,7 @@ pub fn hashAddFrameworks(man: *Cache.Manifest, hm: []const Framework) !void {...@@ -155,7 +155,7 @@ pub fn hashAddFrameworks(man: *Cache.Manifest, hm: []const Framework) !void {
155 for (hm) |value| {155 for (hm) |value| {
156 man.hash.add(value.needed);156 man.hash.add(value.needed);
157 man.hash.add(value.weak);157 man.hash.add(value.weak);
158 _ = try man.addFilePath(value.path, null);158 _ = try man.addInputPath(value.path, .{});
159 }159 }
160}160}
161161
src/main.zig+16-7
...@@ -107,6 +107,7 @@ const normal_usage =...@@ -107,6 +107,7 @@ const normal_usage =
107 \\107 \\
108 \\ env Print lib path, std path, cache directory, and version108 \\ env Print lib path, std path, cache directory, and version
109 \\ help Print this help and exit109 \\ help Print this help and exit
110 \\ cache-cat Print a zig-cache manifest file as zon
110 \\ std View standard library documentation in a browser111 \\ std View standard library documentation in a browser
111 \\ libc Display native libc paths file or validate one112 \\ libc Display native libc paths file or validate one
112 \\ targets List available compilation targets113 \\ targets List available compilation targets
...@@ -242,6 +243,10 @@ const Cmd = enum {...@@ -242,6 +243,10 @@ const Cmd = enum {
242 ar,243 ar,
243244
244 build,245 build,
246 @"cache-cat",
247 fetch,
248 init,
249 libc,
245250
246 clang,251 clang,
247 @"-cc1",252 @"-cc1",
...@@ -258,10 +263,7 @@ const Cmd = enum {...@@ -258,10 +263,7 @@ const Cmd = enum {
258 fmt,263 fmt,
259 objcopy,264 objcopy,
260 objdump,265 objdump,
261 fetch,
262 libc,
263 std,266 std,
264 init,
265 targets,267 targets,
266 version,268 version,
267 env,269 env,
...@@ -351,7 +353,7 @@ fn mainArgs(...@@ -351,7 +353,7 @@ fn mainArgs(
351 dev.check(.ar_command);353 dev.check(.ar_command);
352 return process.exit(try llvmArMain(arena, args));354 return process.exit(try llvmArMain(arena, args));
353 },355 },
354 .build, .fetch, .init, .libc => {356 .build, .fetch, .init, .libc, .@"cache-cat" => {
355 return jitCmd(gpa, arena, io, cmd_args, environ_map, .{357 return jitCmd(gpa, arena, io, cmd_args, environ_map, .{
356 .cmd_name = "maker",358 .cmd_name = "maker",
357 .root_src_path = "Maker.zig",359 .root_src_path = "Maker.zig",
...@@ -3268,6 +3270,7 @@ fn buildOutputType(...@@ -3268,6 +3270,7 @@ fn buildOutputType(
3268 };3270 };
32693271
3270 const cwd_path = try std.zig.getResolvedCwd(io, arena);3272 const cwd_path = try std.zig.getResolvedCwd(io, arena);
3273 std.log.debug("cwd_path={s}", .{cwd_path});
32713274
3272 // This `init` calls `fatal` on error.3275 // This `init` calls `fatal` on error.
3273 var dirs: std.zig.Directories = .init(arena, io, .{3276 var dirs: std.zig.Directories = .init(arena, io, .{
...@@ -4876,8 +4879,14 @@ fn cmdTranslateC(...@@ -4876,8 +4879,14 @@ fn cmdTranslateC(
4876 Compilation.cache_helpers.hashCSource(&man, c_source_file) catch |err|4879 Compilation.cache_helpers.hashCSource(&man, c_source_file) catch |err|
4877 fatal("unable to process {q}: {t}", .{ c_source_file.src_path, err });4880 fatal("unable to process {q}: {t}", .{ c_source_file.src_path, err });
48784881
4879 const result: Compilation.TranslateCResult = if (try man.hit(prog_node)) .{4882 var diag: Cache.Manifest.CheckDiagnostic = undefined;
4880 .digest = man.finalBin(),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(),
4881 .cache_hit = true,4890 .cache_hit = true,
4882 .errors = std.zig.ErrorBundle.empty,4891 .errors = std.zig.ErrorBundle.empty,
4883 } else result: {4892 } else result: {
...@@ -4904,7 +4913,7 @@ fn cmdTranslateC(...@@ -4904,7 +4913,7 @@ fn cmdTranslateC(
4904 }4913 }
4905 }4914 }
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});
4908 break :result result;4917 break :result result;
4909 };4918 };
49104919
test/cases/compile_errors/accessing_runtime_parameter_from_outer_function.zig+1-1
...@@ -14,5 +14,5 @@ export fn entry() void {...@@ -14,5 +14,5 @@ export fn entry() void {
1414
15// error15// error
16//16//
17// :4:24: error: 'y' not accessible from inner function17// :4:24: error: "y" not accessible from inner function
18// :3:9: note: crossed function definition here18// :3:9: note: crossed function definition here
test/cases/compile_errors/bad_import.zig+1-1
...@@ -4,5 +4,5 @@ const bogus = @import(...@@ -4,5 +4,5 @@ const bogus = @import(
44
5// error5// error
6//6//
7// bogus-does-not-exist.zig:1:1: error: unable to load 'bogus-does-not-exist.zig': FileNotFound7// bogus-does-not-exist.zig:1:1: error: unable to load "bogus-does-not-exist.zig": FileNotFound
8// :2:5: note: file imported here8// :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 {...@@ -6,4 +6,4 @@ export fn entry() usize {
66
7// error7// error
8//8//
9// :1:29: error: unable to open 'bogus.txt': FileNotFound9// :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();...@@ -10,7 +10,7 @@ const io = std.Io.Threaded.global_single_threaded.io();
10comptime {10comptime {
11 _ = @import("bad.zig");11 _ = @import("bad.zig");
12}12}
13#expect_error=bad.zig:1:1: error: unable to load 'bad.zig': FileNotFound13#expect_error=bad.zig:1:1: error: unable to load "bad.zig": FileNotFound
14#expect_error=foo.zig:2:17: note: file imported here14#expect_error=foo.zig:2:17: note: file imported here
1515
16#update=change bad import16#update=change bad import
...@@ -18,7 +18,7 @@ comptime {...@@ -18,7 +18,7 @@ comptime {
18comptime {18comptime {
19 _ = @import("this_is/not_real.zig");19 _ = @import("this_is/not_real.zig");
20}20}
21#expect_error=this_is/not_real.zig:1:1: error: unable to load 'not_real.zig': FileNotFound21#expect_error=this_is/not_real.zig:1:1: error: unable to load "not_real.zig": FileNotFound
22#expect_error=foo.zig:2:17: note: file imported here22#expect_error=foo.zig:2:17: note: file imported here
2323
24#update=remove import of 'foo.zig'24#update=remove import of 'foo.zig'
test/incremental/change_embed_file+2-2
...@@ -17,7 +17,7 @@ Hello again, World!...@@ -17,7 +17,7 @@ Hello again, World!
1717
18#update=delete file18#update=delete file
19#rm_file=string.txt19#rm_file=string.txt
20#expect_error=main.zig:2:27: error: unable to open 'string.txt': FileNotFound20#expect_error=main.zig:2:27: error: failed opening "string.txt": FileNotFound
2121
22#update=remove reference to file22#update=remove reference to file
23#file=main.zig23#file=main.zig
...@@ -37,7 +37,7 @@ pub fn main() !void {...@@ -37,7 +37,7 @@ pub fn main() !void {
37 try std.Io.File.stdout().writeStreamingAll(io, string);37 try std.Io.File.stdout().writeStreamingAll(io, string);
38}38}
39const io = std.Io.Threaded.global_single_threaded.io();39const io = std.Io.Threaded.global_single_threaded.io();
40#expect_error=main.zig:2:27: error: unable to open 'string.txt': FileNotFound40#expect_error=main.zig:2:27: error: failed opening "string.txt": FileNotFound
4141
42#update=recreate file42#update=recreate file
43#file=string.txt43#file=string.txt
test/incremental/change_zon_file+2-2
...@@ -17,7 +17,7 @@ const io = std.Io.Threaded.global_single_threaded.io();...@@ -17,7 +17,7 @@ const io = std.Io.Threaded.global_single_threaded.io();
1717
18#update=delete file18#update=delete file
19#rm_file=message.zon19#rm_file=message.zon
20#expect_error=message.zon:1:1: error: unable to load 'message.zon': FileNotFound20#expect_error=message.zon:1:1: error: unable to load "message.zon": FileNotFound
21#expect_error=main.zig:2:37: note: file imported here21#expect_error=main.zig:2:37: note: file imported here
2222
23#update=remove reference to ZON file23#update=remove reference to ZON file
...@@ -28,7 +28,7 @@ pub fn main() !void {...@@ -28,7 +28,7 @@ pub fn main() !void {
28 try std.Io.File.stdout().writeStreamingAll(io, "a hardcoded string\n");28 try std.Io.File.stdout().writeStreamingAll(io, "a hardcoded string\n");
29}29}
30const io = std.Io.Threaded.global_single_threaded.io();30const io = std.Io.Threaded.global_single_threaded.io();
31#expect_error=message.zon:1:1: error: unable to load 'message.zon': FileNotFound31#expect_error=message.zon:1:1: error: unable to load "message.zon": FileNotFound
32#expect_error=main.zig:2:37: note: file imported here32#expect_error=main.zig:2:37: note: file imported here
3333
34#update=recreate ZON file34#update=recreate ZON file
test/src/Cases.zig+2-2
...@@ -319,7 +319,7 @@ pub fn addCompile(...@@ -319,7 +319,7 @@ pub fn addCompile(
319pub fn addFromDir(ctx: *Cases, dir: Io.Dir, path_from_root: []const u8, b: *std.Build) void {319pub fn addFromDir(ctx: *Cases, dir: Io.Dir, path_from_root: []const u8, b: *std.Build) void {
320 var current_file: []const u8 = "none";320 var current_file: []const u8 = "none";
321 ctx.addFromDirInner(dir, path_from_root, &current_file, b) catch |err| {321 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}", .{
323 current_file, err,323 current_file, err,
324 });324 });
325 };325 };
...@@ -348,7 +348,7 @@ fn addFromDirInner(...@@ -348,7 +348,7 @@ fn addFromDirInner(
348 try filenames.append(ctx.arena, try ctx.arena.dupe(u8, entry.path));348 try filenames.append(ctx.arena, try ctx.arena.dupe(u8, entry.path));
349 },349 },
350 .directory => {350 .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 })));
352 },352 },
353 else => continue,353 else => continue,
354 }354 }
test/tests.zig+3-3
...@@ -3339,7 +3339,7 @@ pub fn addCases(...@@ -3339,7 +3339,7 @@ pub fn addCases(
33393339
3340 var cases = @import("src/Cases.zig").init(gpa, arena, io);3340 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
3344 var dir = try b.root.openDir(io, "test/cases", .{ .iterate = true });3344 var dir = try b.root.openDir(io, "test/cases", .{ .iterate = true });
3345 defer dir.close(io);3345 defer dir.close(io);
...@@ -3401,7 +3401,7 @@ pub fn addIncrementalTests(...@@ -3401,7 +3401,7 @@ pub fn addIncrementalTests(
3401 }),3401 }),
3402 });3402 });
34033403
3404 b.dependOnDirectory(b.path("test/incremental"));3404 b.dependOnDirectoryContents(b.path("test/incremental"));
34053405
3406 var dir = try b.root.openDir(io, "test/incremental", .{ .iterate = true });3406 var dir = try b.root.openDir(io, "test/incremental", .{ .iterate = true });
3407 defer dir.close(io);3407 defer dir.close(io);
...@@ -3417,7 +3417,7 @@ pub fn addIncrementalTests(...@@ -3417,7 +3417,7 @@ pub fn addIncrementalTests(
3417 switch (entry.kind) {3417 switch (entry.kind) {
3418 .file => {},3418 .file => {},
3419 .directory => {3419 .directory => {
3420 b.dependOnDirectory(b.path(b.pathJoin(&.{ "test", "incremental", entry.path })));3420 b.dependOnDirectoryContents(b.path(b.pathJoin(&.{ "test", "incremental", entry.path })));
3421 },3421 },
3422 else => continue,3422 else => continue,
3423 }3423 }
tools/incr-check.zig+32-20
...@@ -152,18 +152,12 @@ pub fn main(init: std.process.Init) !void {...@@ -152,18 +152,12 @@ pub fn main(init: std.process.Init) !void {
152152
153 var child_args: std.ArrayList([]const u8) = .empty;153 var child_args: std.ArrayList([]const u8) = .empty;
154 try child_args.appendSlice(arena, &.{154 try child_args.appendSlice(arena, &.{
155 resolved_zig_exe,155 resolved_zig_exe, "build-exe",
156 "build-exe",156 "-fincremental", "-fno-ubsan-rt",
157 "-fincremental",157 "-target", try target_query.zigTriple(arena),
158 "-fno-ubsan-rt",158 "--cache-dir", ".local-cache",
159 "-target",159 "--global-cache-dir", ".global-cache",
160 try target_query.zigTriple(arena),
161 "--cache-dir",
162 ".local-cache",
163 "--global-cache-dir",
164 ".global-cache",
165 });160 });
166 try child_args.append(arena, "--listen=-");
167161
168 if (opt_resolved_lib_dir) |resolved_lib_dir| {162 if (opt_resolved_lib_dir) |resolved_lib_dir| {
169 try child_args.appendSlice(arena, &.{ "--zig-lib-dir", resolved_lib_dir });163 try child_args.appendSlice(arena, &.{ "--zig-lib-dir", resolved_lib_dir });
...@@ -187,6 +181,7 @@ pub fn main(init: std.process.Init) !void {...@@ -187,6 +181,7 @@ pub fn main(init: std.process.Init) !void {
187 for (case.modules) |mod| {181 for (case.modules) |mod| {
188 try child_args.append(arena, try std.fmt.allocPrint(arena, "-M{s}={s}", .{ mod.name, mod.file }));182 try child_args.append(arena, try std.fmt.allocPrint(arena, "-M{s}={s}", .{ mod.name, mod.file }));
189 }183 }
184 try child_args.append(arena, "--listen=-");
190185
191 const zig_prog_node = prog_node.start("zig", 0);186 const zig_prog_node = prog_node.start("zig", 0);
192 defer zig_prog_node.end();187 defer zig_prog_node.end();
...@@ -199,15 +194,23 @@ pub fn main(init: std.process.Init) !void {...@@ -199,15 +194,23 @@ pub fn main(init: std.process.Init) !void {
199 resolved_zig_exe;194 resolved_zig_exe;
200195
201 try cc_child_args.appendSlice(arena, &.{196 try cc_child_args.appendSlice(arena, &.{
202 resolved_cc_zig_exe,197 resolved_cc_zig_exe, "build-exe",
203 "cc",198 "-target", try target_query.zigTriple(arena),
204 "-target",199 "--cache-dir", ".local-cache",
205 try target_query.zigTriple(arena),200 "--global-cache-dir", ".global-cache",
206 "-I",201 "-I", opt_resolved_lib_dir.?, // verified earlier
207 opt_resolved_lib_dir.?, // verified earlier202 "-lc",
208 });203 });
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});
211 }214 }
212215
213 var child = try std.process.spawn(io, .{216 var child = try std.process.spawn(io, .{
...@@ -233,7 +236,7 @@ pub fn main(init: std.process.Init) !void {...@@ -233,7 +236,7 @@ pub fn main(init: std.process.Init) !void {
233 .tmp_dir = tmp_dir,236 .tmp_dir = tmp_dir,
234 .tmp_dir_path = tmp_dir_path,237 .tmp_dir_path = tmp_dir_path,
235 .child = &child,238 .child = &child,
236 .allow_compiler_stderr = debug_log_args.items.len != 0,239 .allow_compiler_stderr = allow_compiler_stderr,
237 .quiet = quiet,240 .quiet = quiet,
238 .preserve_tmp_on_fatal = preserve_tmp,241 .preserve_tmp_on_fatal = preserve_tmp,
239 .cc_child_args = &cc_child_args,242 .cc_child_args = &cc_child_args,
...@@ -659,9 +662,18 @@ const Eval = struct {...@@ -659,9 +662,18 @@ const Eval = struct {
659 const child_prog_node = prog_node.start("build cbe output", 0);662 const child_prog_node = prog_node.start("build cbe output", 0);
660 defer child_prog_node.end();663 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);
663 defer eval.cc_child_args.items.len -= 2;667 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
665 const result = std.process.run(eval.arena, eval.io, .{677 const result = std.process.run(eval.arena, eval.io, .{
666 .argv = eval.cc_child_args.items,678 .argv = eval.cc_child_args.items,
667 .cwd = .{ .path = eval.tmp_dir_path },679 .cwd = .{ .path = eval.tmp_dir_path },