| author | |
| committer | |
| log | b0e7ebc24fdbd17f01180450c0e12f6187ef3f27 |
| tree | 4831e5bc73455a6e6e6cac4f799a9fd5f9992e94 |
| parent | b7104c95f249e42d0068bc2a94f7f426c0c740f3 |
| parent | 56571a70d99a896777146e9588d9f6a04124c9a5 |
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/3683240 files changed, 2744 insertions(+), 1537 deletions(-)
build.zig+15-11| ... | ... | @@ -263,8 +263,8 @@ pub fn build(b: *std.Build) !void { |
| 263 | 263 | const opt_version_string = b.option([]const u8, "version-string", "Override Zig version string. Default is to find out with git."); |
| 264 | 264 | const version_slice = if (opt_version_string) |version| version else v: { |
| 265 | 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", .{}); | |
| 267 | std.process.exit(1); | |
| 266 | std.log.info("version info can be provided explicitly via \"-Dversion-string\"", .{}); | |
| 267 | std.process.fatal("version info cannot be retrieved from git", .{}); | |
| 268 | 268 | } |
| 269 | 269 | |
| 270 | 270 | // Ensure git version changes get picked up. |
| ... | ... | @@ -272,12 +272,13 @@ pub fn build(b: *std.Build) !void { |
| 272 | 272 | const io = b.graph.io; |
| 273 | 273 | const git_file = b.root.openFile(io, ".git", .{ .allow_directory = false }) catch |err| switch (err) { |
| 274 | 274 | error.IsDir => { |
| 275 | b.dependOnFileContents(b.path(".git/logs/HEAD")); | |
| 275 | b.dependOnFileMetadata(b.path(".git/logs/HEAD")); | |
| 276 | 276 | break :git; |
| 277 | 277 | }, |
| 278 | 278 | else => |e| return e, |
| 279 | 279 | }; |
| 280 | 280 | defer git_file.close(io); |
| 281 | b.dependOnFileContents(b.path(".git")); | |
| 281 | 282 | var line_buffer: ["gitdir: ".len + std.Io.Dir.max_path_bytes + 1]u8 = undefined; |
| 282 | 283 | var git_file_reader = git_file.reader(io, &line_buffer); |
| 283 | 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 | 286 | .limited("gitdir: ".len + std.Io.Dir.max_path_bytes + "\r\n".len), |
| 286 | 287 | ), "\r\n"), "gitdir: ")) |git_dir| { |
| 287 | 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 | 290 | b.graph.cwdRelativePath(head_file) |
| 290 | 291 | else |
| 291 | 292 | b.path(head_file)); |
| ... | ... | @@ -310,8 +311,9 @@ pub fn build(b: *std.Build) !void { |
| 310 | 311 | 0 => { |
| 311 | 312 | // Tagged release version (e.g. 0.10.0). |
| 312 | 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.exit(1); | |
| 314 | std.process.fatal("zig version {q} does not match Git tag {q}", .{ | |
| 315 | version_string, git_describe, | |
| 316 | }); | |
| 315 | 317 | } |
| 316 | 318 | break :v version_string; |
| 317 | 319 | }, |
| ... | ... | @@ -324,13 +326,14 @@ pub fn build(b: *std.Build) !void { |
| 324 | 326 | |
| 325 | 327 | const ancestor_ver = try std.SemanticVersion.parse(tagged_ancestor); |
| 326 | 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 }); | |
| 328 | std.process.exit(1); | |
| 329 | std.process.fatal("zig version {f} must be greater than tagged ancestor {qf}", .{ | |
| 330 | zig_version, ancestor_ver, | |
| 331 | }); | |
| 329 | 332 | } |
| 330 | 333 | |
| 331 | 334 | // Check that the commit hash is prefixed with a 'g' (a Git convention). |
| 332 | 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 | 337 | break :v version_string; |
| 335 | 338 | } |
| 336 | 339 | |
| ... | ... | @@ -338,7 +341,7 @@ pub fn build(b: *std.Build) !void { |
| 338 | 341 | break :v b.fmt("{s}-dev.{s}+{s}", .{ version_string, commit_height, commit_id[1..] }); |
| 339 | 342 | }, |
| 340 | 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 | 345 | break :v version_string; |
| 343 | 346 | }, |
| 344 | 347 | } |
| ... | ... | @@ -354,7 +357,8 @@ pub fn build(b: *std.Build) !void { |
| 354 | 357 | const file_contents = cwd.readFileAlloc(io, config_h_path, arena, .limited(max_config_h_bytes)) catch unreachable; |
| 355 | 358 | break :blk parseConfigH(b, file_contents); |
| 356 | 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 | 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 | 199 | .random_seed = parseRandomSeed(seed_arg), |
| 200 | 200 | }; |
| 201 | 201 | |
| 202 | const cmd = stringToEnum(enum { libc, init, fetch, build }, cmd_name) orelse | |
| 202 | const cmd = stringToEnum(enum { libc, init, fetch, build, @"cache-cat" }, cmd_name) orelse | |
| 203 | 203 | fatal("bad command name: {q}", .{cmd_name}); |
| 204 | 204 | switch (cmd) { |
| 205 | 205 | .libc => return cmdLibC(gpa, &graph, args[arg_i..]), |
| 206 | 206 | .init => return cmdInit(gpa, &graph, args[arg_i..]), |
| 207 | 207 | .fetch => return cmdFetch(gpa, &graph, args[arg_i..]), |
| 208 | .@"cache-cat" => return cmdCacheCat(gpa, &graph, args[arg_i..]), | |
| 208 | 209 | .build => {}, |
| 209 | 210 | } |
| 210 | 211 | |
| ... | ... | @@ -702,8 +703,8 @@ pub fn main(init: process.Init.Minimal) !void { |
| 702 | 703 | configure: while (true) { |
| 703 | 704 | // Set of files that, if modified, imply that recompiling and rerunning |
| 704 | 705 | // configurer is needed. |
| 705 | var configure_source_files: Cache.Manifest.Files = .empty; | |
| 706 | defer Cache.Manifest.freeFiles(gpa, &configure_source_files); | |
| 706 | var configure_source_files: Cache.Manifest.SelfContainedFiles = .empty; | |
| 707 | defer configure_source_files.deinit(gpa); | |
| 707 | 708 | |
| 708 | 709 | // If this fails, we can still start the server and wait for user |
| 709 | 710 | // to request a rebuild. If it returns error.FailedButCacheIntact |
| ... | ... | @@ -1043,7 +1044,7 @@ const ConfigureOptions = struct { |
| 1043 | 1044 | fetch_only: bool, |
| 1044 | 1045 | print_configuration: PrintConfiguration, |
| 1045 | 1046 | forks: []Fork, |
| 1046 | src_files: *Cache.Manifest.Files, | |
| 1047 | src_files: *Cache.Manifest.SelfContainedFiles, | |
| 1047 | 1048 | }; |
| 1048 | 1049 | |
| 1049 | 1050 | fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { |
| ... | ... | @@ -1398,8 +1399,14 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { |
| 1398 | 1399 | defer compile_prog_node.end(); |
| 1399 | 1400 | |
| 1400 | 1401 | if (config_man) |man| { |
| 1401 | if (try man.hit(compile_prog_node)) { | |
| 1402 | const digest = man.final(); | |
| 1402 | var diagnostic: Cache.Manifest.CheckDiagnostic = undefined; | |
| 1403 | const status = man.check(&diagnostic, compile_prog_node) catch |err| switch (err) { | |
| 1404 | error.Canceled, error.OutOfMemory => |e| return e, | |
| 1405 | error.CacheCheckFailed => fatal("checking cache failed: {f}", .{diagnostic.fmt(man)}), | |
| 1406 | }; | |
| 1407 | log.debug("configuration cache {f}", .{status.fmt(man)}); | |
| 1408 | if (status == .hit) { | |
| 1409 | const digest = man.hitDigestHex(); | |
| 1403 | 1410 | const path: Path = .{ |
| 1404 | 1411 | .root_dir = graph.local_cache_root, |
| 1405 | 1412 | .sub_path = try arena.print("c/{s}", .{&digest}), |
| ... | ... | @@ -1497,11 +1504,12 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { |
| 1497 | 1504 | } |
| 1498 | 1505 | |
| 1499 | 1506 | if (config_man) |man| for (configuration.path_deps) |path_dep| { |
| 1500 | switch (path_dep.flags.mode) { | |
| 1501 | .directory => {}, // TODO | |
| 1502 | .contents => try man.addPathPost(try confPathDepToCachePath(arena, graph, &configuration, path_dep)), | |
| 1503 | .metadata => {}, // TODO | |
| 1504 | } | |
| 1507 | const path = try confPathDepToCachePath(arena, graph, &configuration, path_dep); | |
| 1508 | try man.addDiscoveredPath(.{ | |
| 1509 | .discovered_path = .{ .unresolved = path }, | |
| 1510 | .handle = if (path_dep.flags.is_directory) .{ .dir = null } else .{ .file = null }, | |
| 1511 | .metadata_only = path_dep.flags.metadata_only, | |
| 1512 | }); | |
| 1505 | 1513 | }; |
| 1506 | 1514 | |
| 1507 | 1515 | // 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 | 1518 | break :cp .{ config_tmp_path, null }; |
| 1511 | 1519 | } else { |
| 1512 | 1520 | const man = config_man.?; |
| 1513 | const digest = man.final(); | |
| 1521 | const digest = man.missDigestHex(); | |
| 1514 | 1522 | const final_path: Path = .{ |
| 1515 | 1523 | .root_dir = graph.local_cache_root, |
| 1516 | 1524 | .sub_path = try arena.print("c/{s}", .{&digest}), |
| ... | ... | @@ -1541,7 +1549,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { |
| 1541 | 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 | 1553 | options.src_files.* = man.takeFiles(); |
| 1546 | 1554 | break :cp .{ final_path, man.toOwnedLock() }; |
| 1547 | 1555 | } |
| ... | ... | @@ -1563,10 +1571,10 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { |
| 1563 | 1571 | |
| 1564 | 1572 | const configuration = c: { |
| 1565 | 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 | 1575 | defer file.close(io); |
| 1568 | 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 | 1579 | // Technically if the configuration is marked as poisoned, we could |
| 1572 | 1580 | // 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 | 1887 | return process.cleanExit(io); |
| 1880 | 1888 | } |
| 1881 | 1889 | |
| 1890 | fn 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 | ||
| 1928 | fn 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 | ||
| 1974 | const 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 | ||
| 1882 | 1985 | const usage_fetch = |
| 1883 | 1986 | \\Usage: zig fetch [options] <url> |
| 1884 | 1987 | \\Usage: zig fetch [options] <path> |
| ... | ... | @@ -2220,7 +2323,7 @@ fn resolveTopLevelSteps(maker: *Maker, step_names: []const []const u8) ![]const |
| 2220 | 2323 | fn prepare( |
| 2221 | 2324 | maker: *Maker, |
| 2222 | 2325 | step_indices: []const Configuration.Step.Index, |
| 2223 | configure_source_files: *const Cache.Manifest.Files, | |
| 2326 | configure_source_files: *const Cache.Manifest.SelfContainedFiles, | |
| 2224 | 2327 | ) !void { |
| 2225 | 2328 | const gpa = maker.gpa; |
| 2226 | 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 | 726 | /// Prefer `cacheHitWatched` unless you already added watch inputs |
| 727 | 727 | /// separately from using the cache system. |
| 728 | 728 | pub fn cacheHit(s: *Step, maker: *Maker, man: *Cache.Manifest, parent_progress_node: std.Progress.Node) !bool { |
| 729 | s.result_cached = man.hit(parent_progress_node) catch |err| return failWithCacheError(s, maker, man, err); | |
| 730 | return s.result_cached; | |
| 729 | var diag: Cache.Manifest.CheckDiagnostic = undefined; | |
| 730 | const status = man.check(&diag, parent_progress_node) catch |err| | |
| 731 | return failWithCacheError(s, maker, man, &diag, err); | |
| 732 | const hit = .hit == status; // TODO cache miss reason in the build summary | |
| 733 | s.result_cached = hit; | |
| 734 | return hit; | |
| 731 | 735 | } |
| 732 | 736 | |
| 733 | 737 | /// Clears previous watch inputs, if any, and then populates watch inputs from |
| 734 | 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`. | |
| 737 | 741 | pub fn cacheHitWatched(s: *Step, maker: *Maker, man: *Cache.Manifest, parent_progress_node: std.Progress.Node) !bool { |
| 738 | const is_hit = man.hit(parent_progress_node) catch |err| return failWithCacheError(s, maker, man, err); | |
| 739 | s.result_cached = is_hit; | |
| 742 | var diag: Cache.Manifest.CheckDiagnostic = undefined; | |
| 743 | const status = man.check(&diag, parent_progress_node) catch |err| | |
| 744 | return failWithCacheError(s, maker, man, &diag, err); | |
| 745 | const hit = .hit == status; // TODO cache miss reason in the build summary | |
| 746 | s.result_cached = hit; | |
| 740 | 747 | // The above call to hit() populates the manifest with files, so in case of |
| 741 | 748 | // a hit, we need to populate watch inputs. |
| 742 | if (is_hit) try setWatchInputsFromManifest(s, maker, man); | |
| 743 | return is_hit; | |
| 749 | if (hit) try setWatchInputsFromManifest(s, maker, man); | |
| 750 | return hit; | |
| 744 | 751 | } |
| 745 | 752 | |
| 746 | 753 | fn failWithCacheError( |
| 747 | 754 | s: *Step, |
| 748 | 755 | maker: *Maker, |
| 749 | 756 | man: *const Cache.Manifest, |
| 750 | err: Cache.Manifest.HitError, | |
| 757 | diag: *const Cache.Manifest.CheckDiagnostic, | |
| 758 | err: Cache.Manifest.CheckError, | |
| 751 | 759 | ) error{ OutOfMemory, Canceled, MakeFailed } { |
| 752 | 760 | switch (err) { |
| 753 | error.CacheCheckFailed => switch (man.diagnostic) { | |
| 754 | .none => unreachable, | |
| 755 | .manifest_create, .manifest_read, .manifest_lock => |e| return s.fail(maker, "failed checking cache: {t} {t}", .{ | |
| 756 | man.diagnostic, e, | |
| 757 | }), | |
| 758 | .file_open, .file_stat, .file_read, .file_hash => |op| { | |
| 759 | const pp = man.files.keys()[op.file_index].prefixed_path; | |
| 760 | const prefix = man.cache.prefixes()[pp.prefix].path orelse ""; | |
| 761 | return s.fail(maker, "failed checking cache: {s}{c}{s} {t} {t}", .{ | |
| 762 | prefix, Dir.path.sep, pp.sub_path, man.diagnostic, op.err, | |
| 763 | }); | |
| 764 | }, | |
| 765 | }, | |
| 761 | error.CacheCheckFailed => return s.fail(maker, "checking cache failed: {f}", .{diag.fmt(man)}), | |
| 766 | 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 | } |
| 770 | 765 | |
| 771 | /// Prefer `writeManifestAndWatch` unless you already added watch inputs | |
| 766 | /// Prefer `finalizeManifestAndWatch` unless you already added watch inputs | |
| 772 | 767 | /// separately from using the cache system. |
| 773 | pub fn writeManifest(s: *Step, maker: *Maker, man: *Cache.Manifest) !void { | |
| 768 | pub fn finalizeManifest(s: *Step, maker: *Maker, man: *Cache.Manifest) !void { | |
| 774 | 769 | if (s.test_results.isSuccess()) { |
| 775 | man.writeManifest() catch |err| switch (err) { | |
| 770 | man.finalize() catch |err| switch (err) { | |
| 776 | 771 | error.Canceled => |e| return e, |
| 777 | 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 | 778 | /// the full set of files picked up by the cache manifest. |
| 784 | 779 | /// |
| 785 | 780 | /// Must be accompanied with `cacheHitWatched`. |
| 786 | pub fn writeManifestAndWatch(s: *Step, maker: *Maker, man: *Cache.Manifest) !void { | |
| 787 | try writeManifest(s, maker, man); | |
| 781 | pub fn finalizeManifestAndWatch(s: *Step, maker: *Maker, man: *Cache.Manifest) !void { | |
| 782 | try finalizeManifest(s, maker, man); | |
| 788 | 783 | try setWatchInputsFromManifest(s, maker, man); |
| 789 | 784 | } |
| 790 | 785 | |
| 791 | 786 | pub 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 | } |
| 794 | 789 | |
| 795 | 790 | pub fn setWatchInputsFromManifestFiles( |
| 796 | 791 | s: *Step, |
| 797 | 792 | maker: *Maker, |
| 798 | files: *const Cache.Manifest.Files, | |
| 793 | scf: *const Cache.Manifest.SelfContainedFiles, | |
| 799 | 794 | prefixes: []const Cache.Directory, |
| 800 | 795 | ) !void { |
| 801 | 796 | const graph = maker.graph; |
| 802 | 797 | const arena = graph.arena; // TODO don't leak into process arena |
| 803 | 798 | clearWatchInputs(s, maker); |
| 804 | for (files.keys()) |file| { | |
| 799 | for (scf.files.keys()) |file_offset| { | |
| 805 | 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 | 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 | 804 | .sub_path = Dir.path.dirname(sub_path) orelse "", |
| 810 | 805 | }, Dir.path.basename(sub_path)); |
| 811 | 806 | } |
lib/compiler/Maker/Step/ConfigHeader.zig+3-3| ... | ... | @@ -137,7 +137,7 @@ pub fn make( |
| 137 | 137 | man.hash.addBytes(output); |
| 138 | 138 | |
| 139 | 139 | if (try step.cacheHit(maker, &man, progress_node)) { |
| 140 | const digest = man.final(); | |
| 140 | const digest = man.hitDigestHex(); | |
| 141 | 141 | maker.generatedPath(conf_ch.generated_dir).* = .{ |
| 142 | 142 | .root_dir = cache_root, |
| 143 | 143 | .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }), |
| ... | ... | @@ -145,7 +145,7 @@ pub fn make( |
| 145 | 145 | return; |
| 146 | 146 | } |
| 147 | 147 | |
| 148 | const digest = man.final(); | |
| 148 | const digest = man.missDigestHex(); | |
| 149 | 149 | |
| 150 | 150 | // If output_path has directory parts, deal with them. Example: |
| 151 | 151 | // output_dir is zig-cache/o/HASH |
| ... | ... | @@ -169,7 +169,7 @@ pub fn make( |
| 169 | 169 | .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }), |
| 170 | 170 | }; |
| 171 | 171 | |
| 172 | try step.writeManifest(maker, &man); | |
| 172 | try step.finalizeManifest(maker, &man); | |
| 173 | 173 | } |
| 174 | 174 | |
| 175 | 175 | fn ensureAllValuesUsed( |
lib/compiler/Maker/Step/ObjCopy.zig+4-4| ... | ... | @@ -34,7 +34,7 @@ pub fn make( |
| 34 | 34 | defer man.deinit(); |
| 35 | 35 | |
| 36 | 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 | 38 | man.hash.addOptionalBytes(only_section); |
| 39 | 39 | man.hash.addOptionalBytes(opt_basename); |
| 40 | 40 | man.hash.addOptionalBytes(opt_debug_basename); |
| ... | ... | @@ -48,7 +48,7 @@ pub fn make( |
| 48 | 48 | |
| 49 | 49 | if (try step.cacheHit(maker, &man, progress_node)) { |
| 50 | 50 | // Cache hit, skip subprocess execution. |
| 51 | const digest = man.final(); | |
| 51 | const digest = man.hitDigestHex(); | |
| 52 | 52 | maker.generatedPath(conf_oc.output_file).* = .{ |
| 53 | 53 | .root_dir = cache_root, |
| 54 | 54 | .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }), |
| ... | ... | @@ -67,7 +67,7 @@ pub fn make( |
| 67 | 67 | |
| 68 | 68 | // We don't find out more input files while executing objcopy so we can |
| 69 | 69 | // already obtain the digest and use it directly as the output path. |
| 70 | const digest = man.final(); | |
| 70 | const digest = man.missDigestHex(); | |
| 71 | 71 | const dest_path: Path = .{ |
| 72 | 72 | .root_dir = cache_root, |
| 73 | 73 | .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }), |
| ... | ... | @@ -165,7 +165,7 @@ pub fn make( |
| 165 | 165 | |
| 166 | 166 | maker.generatedPath(conf_oc.output_file).* = dest_path; |
| 167 | 167 | |
| 168 | step.writeManifest(maker, &man) catch |err| switch (err) { | |
| 168 | step.finalizeManifest(maker, &man) catch |err| switch (err) { | |
| 169 | 169 | error.Canceled => |e| return e, |
| 170 | 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 | 42 | const lazy_path = arg.path.get(conf); |
| 43 | 43 | try step.addWatchInput(maker, arena, lazy_path); |
| 44 | 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 | 46 | try args_bytes.print(arena, "pub const {f}: []const u8 = \"{f}\";\n", .{ |
| 47 | 47 | std.zig.fmtId(name), arg_path.fmtEscapeString(), |
| 48 | 48 | }); |
| ... | ... | @@ -54,7 +54,7 @@ pub fn make( |
| 54 | 54 | const basename = "options.zig"; |
| 55 | 55 | |
| 56 | 56 | if (try step.cacheHitWatched(maker, &man, progress_node)) { |
| 57 | const digest = man.final(); | |
| 57 | const digest = man.hitDigestHex(); | |
| 58 | 58 | maker.generatedPath(conf_options.generated_file).* = .{ |
| 59 | 59 | .root_dir = cache_root, |
| 60 | 60 | .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }), |
| ... | ... | @@ -63,7 +63,7 @@ pub fn make( |
| 63 | 63 | return; |
| 64 | 64 | } |
| 65 | 65 | |
| 66 | const digest = man.final(); | |
| 66 | const digest = man.missDigestHex(); | |
| 67 | 67 | const out_path: Cache.Path = .{ |
| 68 | 68 | .root_dir = cache_root, |
| 69 | 69 | .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }), |
| ... | ... | @@ -95,7 +95,7 @@ pub fn make( |
| 95 | 95 | }, |
| 96 | 96 | }; |
| 97 | 97 | |
| 98 | try step.writeManifestAndWatch(maker, &man); | |
| 98 | try step.finalizeManifestAndWatch(maker, &man); | |
| 99 | 99 | |
| 100 | 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 | 97 | man.hash.add(arg.flags.make_absolute); |
| 98 | 98 | man.hash.addBytesZ(prefix); |
| 99 | 99 | man.hash.addBytesZ(suffix); |
| 100 | _ = try man.addFilePath(file_path, null); | |
| 100 | _ = try man.addInputPath(file_path, .{}); | |
| 101 | 101 | }, |
| 102 | 102 | .path_directory => { |
| 103 | 103 | const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; |
| ... | ... | @@ -135,7 +135,7 @@ pub fn make( |
| 135 | 135 | argv_list.appendAssumeCapacity(result.written()); |
| 136 | 136 | man.hash.addBytesZ(prefix); |
| 137 | 137 | man.hash.addBytesZ(suffix); |
| 138 | _ = try man.addFilePath(file_path, null); | |
| 138 | _ = try man.addInputPath(file_path, .{}); | |
| 139 | 139 | }, |
| 140 | 140 | .artifact => { |
| 141 | 141 | const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; |
| ... | ... | @@ -155,7 +155,7 @@ pub fn make( |
| 155 | 155 | man.hash.add(arg.flags.make_absolute); |
| 156 | 156 | man.hash.addBytesZ(prefix); |
| 157 | 157 | man.hash.addBytesZ(suffix); |
| 158 | _ = try man.addFilePath(file_path, null); | |
| 158 | _ = try man.addInputPath(file_path, .{}); | |
| 159 | 159 | }, |
| 160 | 160 | .output_file, .output_directory => { |
| 161 | 161 | const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; |
| ... | ... | @@ -211,7 +211,7 @@ pub fn make( |
| 211 | 211 | }, |
| 212 | 212 | .lazy_path => |lazy_path| { |
| 213 | 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 | 216 | .none => {}, |
| 217 | 217 | } |
| ... | ... | @@ -240,7 +240,7 @@ pub fn make( |
| 240 | 240 | |
| 241 | 241 | for (conf_run.file_inputs.slice) |lazy_path| { |
| 242 | 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 | } |
| 245 | 245 | |
| 246 | 246 | if (conf_run.cwd.value) |lazy_path| { |
| ... | ... | @@ -261,7 +261,7 @@ pub fn make( |
| 261 | 261 | |
| 262 | 262 | if (!has_side_effects and try step.cacheHitWatched(maker, &man, progress_node)) { |
| 263 | 263 | // Cache hit; skip running command. |
| 264 | const digest = man.final(); | |
| 264 | const digest = man.hitDigestHex(); | |
| 265 | 265 | try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest); |
| 266 | 266 | try populateGeneratedPaths(maker, output_placeholders.items, cache_root, &digest); |
| 267 | 267 | step.result_cached = true; |
| ... | ... | @@ -270,12 +270,12 @@ pub fn make( |
| 270 | 270 | |
| 271 | 271 | if (!any_dep_files) { |
| 272 | 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 | 274 | const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest; |
| 275 | 275 | try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest); |
| 276 | 276 | try populateGeneratedPathsCreateDirs(arena, run_index, maker, output_dir_path, output_placeholders.items, argv_list.items); |
| 277 | 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 | 279 | return; |
| 280 | 280 | } |
| 281 | 281 | |
| ... | ... | @@ -292,23 +292,39 @@ pub fn make( |
| 292 | 292 | switch (arg.flags.tag) { |
| 293 | 293 | .output_file => if (arg.flags.dep_file) { |
| 294 | 294 | const generated_path = maker.generatedPath(arg.generated.value.?).*; |
| 295 | const result = if (has_side_effects) | |
| 296 | man.addDepFile(generated_path.root_dir.handle, generated_path.sub_path) | |
| 297 | else | |
| 298 | man.addDepFilePost(generated_path.root_dir.handle, generated_path.sub_path); | |
| 299 | result catch |err| switch (err) { | |
| 300 | error.OutOfMemory, error.Canceled => |e| return e, | |
| 301 | else => |e| return step.fail(maker, "failed adding to cache the file {f}: {t}", .{ | |
| 302 | generated_path, e, | |
| 303 | }), | |
| 304 | }; | |
| 295 | if (has_side_effects) { | |
| 296 | var diagnostic: Cache.DepTokenizer.Token = undefined; | |
| 297 | man.addInputDepFile(generated_path, &diagnostic) catch |err| switch (err) { | |
| 298 | error.OutOfMemory, error.Canceled => |e| return e, | |
| 299 | error.InvalidDepFile => return step.fail(maker, "failed adding dep file {f} to cache: {f}", .{ | |
| 300 | generated_path, diagnostic, | |
| 301 | }), | |
| 302 | else => |e| return step.fail(maker, "failed adding dep file {f} to cache: {t}", .{ | |
| 303 | generated_path, e, | |
| 304 | }), | |
| 305 | }; | |
| 306 | } else { | |
| 307 | var diagnostic: Cache.Manifest.AddDiscoveredDepFileDiagnostic = undefined; | |
| 308 | man.addDiscoveredDepFile(generated_path, &diagnostic) catch |err| switch (err) { | |
| 309 | error.OutOfMemory, error.Canceled => |e| return e, | |
| 310 | error.InvalidDepFile => return step.fail(maker, "failed adding dep file {f} to cache: {f}", .{ | |
| 311 | generated_path, diagnostic.dep_tokenizer, | |
| 312 | }), | |
| 313 | error.FileSystemFailure => return step.fail(maker, "failed adding a path from dep file {f} to cache: {f}", .{ | |
| 314 | generated_path, diagnostic.add_discovered_path, | |
| 315 | }), | |
| 316 | else => |e| return step.fail(maker, "failed adding dep file {f} to cache: {t}", .{ | |
| 317 | generated_path, e, | |
| 318 | }), | |
| 319 | }; | |
| 320 | } | |
| 305 | 321 | }, |
| 306 | 322 | .output_directory => continue, |
| 307 | 323 | else => unreachable, |
| 308 | 324 | } |
| 309 | 325 | } |
| 310 | 326 | |
| 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(); | |
| 312 | 328 | |
| 313 | 329 | const any_output = output_placeholders.items.len > 0 or |
| 314 | 330 | conf_run.captured_stdout.value != null or conf_run.captured_stderr.value != null; |
| ... | ... | @@ -344,7 +360,7 @@ pub fn make( |
| 344 | 360 | }; |
| 345 | 361 | } |
| 346 | 362 | |
| 347 | if (!has_side_effects) try step.writeManifestAndWatch(maker, &man); | |
| 363 | if (!has_side_effects) try step.finalizeManifestAndWatch(maker, &man); | |
| 348 | 364 | |
| 349 | 365 | try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest); |
| 350 | 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 | 55 | man.hash.addBytes(copy.sub_path.slice(conf)); |
| 56 | 56 | const src_lazy_path = copy.src_file.get(conf); |
| 57 | 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 | 59 | try step.addWatchInput(maker, arena, src_lazy_path); |
| 60 | 60 | } |
| 61 | 61 | |
| ... | ... | @@ -96,7 +96,7 @@ pub fn make( |
| 96 | 96 | }, |
| 97 | 97 | .file => { |
| 98 | 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 | 100 | total_items += 1; |
| 101 | 101 | }, |
| 102 | 102 | else => continue, |
| ... | ... | @@ -105,7 +105,7 @@ pub fn make( |
| 105 | 105 | } |
| 106 | 106 | |
| 107 | 107 | if (try step.cacheHit(maker, &man, progress_node)) { |
| 108 | const digest = man.final(); | |
| 108 | const digest = man.hitDigestHex(); | |
| 109 | 109 | maker.generatedPath(conf_wf.generated_directory).* = .{ |
| 110 | 110 | .root_dir = cache_root, |
| 111 | 111 | .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }), |
| ... | ... | @@ -114,7 +114,7 @@ pub fn make( |
| 114 | 114 | return; |
| 115 | 115 | } |
| 116 | 116 | |
| 117 | const digest = man.final(); | |
| 117 | const digest = man.missDigestHex(); | |
| 118 | 118 | const out_path: Path = .{ |
| 119 | 119 | .root_dir = cache_root, |
| 120 | 120 | .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }), |
| ... | ... | @@ -122,7 +122,7 @@ pub fn make( |
| 122 | 122 | |
| 123 | 123 | progress_node.setEstimatedTotalItems(total_items); |
| 124 | 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); | |
| 126 | 126 | |
| 127 | 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 | 58 | |
| 59 | 59 | pub const ConfigureDependency = struct { |
| 60 | 60 | lazy_path: LazyPath, |
| 61 | mode: std.Build.Configuration.PathDep.Mode, | |
| 61 | is_directory: bool, | |
| 62 | metadata_only: bool, | |
| 62 | 63 | }; |
| 63 | 64 | |
| 64 | 65 | pub const ReleaseMode = enum { |
| ... | ... | @@ -102,7 +103,8 @@ pub const Graph = struct { |
| 102 | 103 | /// Populated by calling one of: |
| 103 | 104 | /// * `dependOnFileContents` |
| 104 | 105 | /// * `dependOnFileMetadata` |
| 105 | /// * `dependOnDirectory` | |
| 106 | /// * `dependOnDirectoryContents` | |
| 107 | /// * `dependOnDirectoryMetadata` | |
| 106 | 108 | configure_dependencies: ArrayList(ConfigureDependency) = .empty, |
| 107 | 109 | |
| 108 | 110 | /// 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 | 2604 | const graph = b.graph; |
| 2603 | 2605 | graph.configure_dependencies.append(graph.arena, .{ |
| 2604 | 2606 | .lazy_path = lazy_path.dupe(graph), |
| 2605 | .mode = .contents, | |
| 2607 | .is_directory = false, | |
| 2608 | .metadata_only = false, | |
| 2606 | 2609 | }) catch @panic("OOM"); |
| 2607 | 2610 | } |
| 2608 | 2611 | |
| ... | ... | @@ -2627,7 +2630,8 @@ pub fn dependOnFileMetadata(b: *Build, lazy_path: LazyPath) void { |
| 2627 | 2630 | const graph = b.graph; |
| 2628 | 2631 | graph.configure_dependencies.append(graph.arena, .{ |
| 2629 | 2632 | .lazy_path = lazy_path.dupe(graph), |
| 2630 | .mode = .metadata, | |
| 2633 | .is_directory = false, | |
| 2634 | .metadata_only = true, | |
| 2631 | 2635 | }) catch @panic("OOM"); |
| 2632 | 2636 | } |
| 2633 | 2637 | |
| ... | ... | @@ -2636,6 +2640,35 @@ pub fn dependOnFileMetadata(b: *Build, lazy_path: LazyPath) void { |
| 2636 | 2640 | /// This is an alternative to `Graph.poisonCache` that avoids making every invocation |
| 2637 | 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. | |
| 2656 | pub 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 | 2672 | /// If any file is created, deleted, or renamed in this directory, the |
| 2640 | 2673 | /// configure phase will be repeated. |
| 2641 | 2674 | /// |
| ... | ... | @@ -2646,12 +2679,15 @@ pub fn dependOnFileMetadata(b: *Build, lazy_path: LazyPath) void { |
| 2646 | 2679 | /// |
| 2647 | 2680 | /// If the directory would be inside one of the search prefixes, then the dependency |
| 2648 | 2681 | /// cannot be tracked; `Graph.poisonCache` must be used instead. |
| 2649 | pub fn dependOnDirectory(b: *Build, lazy_path: LazyPath) void { | |
| 2682 | /// | |
| 2683 | /// Not recursive. | |
| 2684 | pub fn dependOnDirectoryMetadata(b: *Build, lazy_path: LazyPath) void { | |
| 2650 | 2685 | validateConfigureDependency(lazy_path); |
| 2651 | 2686 | const graph = b.graph; |
| 2652 | 2687 | graph.configure_dependencies.append(graph.arena, .{ |
| 2653 | 2688 | .lazy_path = lazy_path.dupe(graph), |
| 2654 | .mode = .directory, | |
| 2689 | .is_directory = true, | |
| 2690 | .metadata_only = true, | |
| 2655 | 2691 | }) catch @panic("OOM"); |
| 2656 | 2692 | } |
| 2657 | 2693 |
lib/std/Build/Cache.zig+1920-885| ... | ... | @@ -48,8 +48,7 @@ pub fn obtain(cache: *Cache) Manifest { |
| 48 | 48 | .cache = cache, |
| 49 | 49 | .hash = cache.hash, |
| 50 | 50 | .manifest_file = null, |
| 51 | .manifest_dirty = false, | |
| 52 | .hex_digest = undefined, | |
| 51 | .state = .input, | |
| 53 | 52 | }; |
| 54 | 53 | } |
| 55 | 54 | |
| ... | ... | @@ -57,71 +56,66 @@ pub fn prefixes(cache: *const Cache) []const Directory { |
| 57 | 56 | return cache.prefixes_buffer[0..cache.prefixes_len]; |
| 58 | 57 | } |
| 59 | 58 | |
| 60 | pub const PrefixedPath = struct { | |
| 61 | prefix: u8, | |
| 62 | sub_path: []const u8, | |
| 63 | ||
| 64 | fn eql(a: PrefixedPath, b: PrefixedPath) bool { | |
| 65 | return a.prefix == b.prefix and std.mem.eql(u8, a.sub_path, b.sub_path); | |
| 66 | } | |
| 59 | const PrefixIndex = u6; | |
| 67 | 60 | |
| 68 | fn hash(pp: PrefixedPath) u32 { | |
| 69 | return @truncate(std.hash.Wyhash.hash(pp.prefix, pp.sub_path)); | |
| 70 | } | |
| 61 | const PrefixedPath = struct { | |
| 62 | prefix: PrefixIndex, | |
| 63 | sub_path: []const u8, | |
| 71 | 64 | }; |
| 72 | 65 | |
| 73 | fn findPrefixPath(cache: *const Cache, path: Path) !PrefixedPath { | |
| 74 | const gpa = cache.gpa; | |
| 75 | const resolved_path = try std.fs.path.resolve(gpa, &.{ | |
| 76 | cache.cwd, path.root_dir.path orelse ".", path.subPathOrDot(), | |
| 77 | }); | |
| 78 | errdefer gpa.free(resolved_path); | |
| 79 | return findPrefixResolved(cache, resolved_path); | |
| 80 | } | |
| 81 | ||
| 82 | fn 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); | |
| 66 | fn appendPrefixedPath(cache: *const Cache, contents: *std.ArrayList(u8), prefixed_path: PrefixedPath) !PrefixIndex { | |
| 67 | const end = contents.items.len + prefixed_path.sub_path.len; | |
| 68 | const needed_alignment = @alignOf(Manifest.File) - (end % @alignOf(Manifest.File)); | |
| 69 | assert(needed_alignment >= 1); // Always need at least a null byte. | |
| 70 | // Extra +1 here is due to the requirement to keep at least 1 unused capacity in `Manifest.contents` at all times. | |
| 71 | try contents.ensureTotalCapacity(cache.gpa, end + needed_alignment + 1); | |
| 72 | contents.appendSliceAssumeCapacity(prefixed_path.sub_path); | |
| 73 | contents.appendNTimesAssumeCapacity(0, needed_alignment); | |
| 74 | return prefixed_path.prefix; | |
| 87 | 75 | } |
| 88 | 76 | |
| 89 | /// Takes ownership of `resolved_path` on success. | |
| 90 | fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath { | |
| 77 | fn resolveAppendPath(cache: *const Cache, contents: *std.ArrayList(u8), path: Path) !PrefixIndex { | |
| 91 | 78 | const gpa = cache.gpa; |
| 92 | 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 | 88 | for (cache.prefixes(), 0..) |prefix, i| { |
| 94 | const p = prefix.path orelse continue; | |
| 95 | const sub_path = getPrefixSubpath(gpa, cwd, p, resolved_path) catch |err| switch (err) { | |
| 96 | error.NotASubPath => continue, | |
| 97 | else => |e| return e, | |
| 98 | }; | |
| 99 | // Free the resolved path since we're not going to return it | |
| 100 | gpa.free(resolved_path); | |
| 101 | return .{ | |
| 102 | .prefix = @intCast(i), | |
| 103 | .sub_path = sub_path, | |
| 104 | }; | |
| 89 | const pp = prefix.path orelse continue; | |
| 90 | contents.shrinkRetainingCapacity(path_start); | |
| 91 | try std.fs.path.relativeAppend(gpa, contents, cwd, null, pp, resolved_path); | |
| 92 | const relative = contents.items[path_start..]; | |
| 93 | ||
| 94 | var component_iterator: std.fs.path.NativeComponentIterator = .init(relative); | |
| 95 | if (component_iterator.root() != null) continue; | |
| 96 | const first_component = component_iterator.first(); | |
| 97 | if (first_component != null and mem.eql(u8, first_component.?.name, "..")) continue; | |
| 98 | ||
| 99 | const needed_alignment = @alignOf(Manifest.File) - (contents.items.len % @alignOf(Manifest.File)); | |
| 100 | assert(needed_alignment >= 1); // Always need at least a null byte. | |
| 101 | // Extra +1 here is due to the requirement to keep at least 1 unused capacity in `Manifest.contents` at all | |
| 102 | // times. | |
| 103 | try contents.ensureUnusedCapacity(gpa, needed_alignment + 1); | |
| 104 | contents.appendNTimesAssumeCapacity(0, needed_alignment); | |
| 105 | ||
| 106 | return @intCast(i); | |
| 105 | 107 | } |
| 106 | 108 | |
| 107 | return .{ | |
| 108 | .prefix = 0, | |
| 109 | .sub_path = resolved_path, | |
| 110 | }; | |
| 111 | } | |
| 109 | contents.shrinkRetainingCapacity(path_start); | |
| 110 | try contents.appendSlice(gpa, resolved_path); | |
| 112 | 111 | |
| 113 | fn getPrefixSubpath(gpa: Allocator, cwd: []const u8, prefix: []const u8, path: []u8) ![]u8 { | |
| 114 | const relative = try std.fs.path.relative(gpa, cwd, null, prefix, path); | |
| 115 | errdefer gpa.free(relative); | |
| 116 | var component_iterator: std.fs.path.NativeComponentIterator = .init(relative); | |
| 117 | if (component_iterator.root() != null) { | |
| 118 | return error.NotASubPath; | |
| 119 | } | |
| 120 | const first_component = component_iterator.first(); | |
| 121 | if (first_component != null and std.mem.eql(u8, first_component.?.name, "..")) { | |
| 122 | return error.NotASubPath; | |
| 123 | } | |
| 124 | return relative; | |
| 112 | const needed_alignment = @alignOf(Manifest.File) - (contents.items.len % @alignOf(Manifest.File)); | |
| 113 | assert(needed_alignment >= 1); // Always need at least a null byte. | |
| 114 | // Extra +1 here is due to the requirement to keep at least 1 unused capacity in `Manifest.contents` at all times. | |
| 115 | try contents.ensureUnusedCapacity(gpa, needed_alignment + 1); | |
| 116 | contents.appendNTimesAssumeCapacity(0, needed_alignment); | |
| 117 | ||
| 118 | return 0; | |
| 125 | 119 | } |
| 126 | 120 | |
| 127 | 121 | /// 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 | 124 | pub const BinDigest = [bin_digest_len]u8; |
| 131 | 125 | pub const HexDigest = [hex_digest_len]u8; |
| 132 | 126 | |
| 133 | /// This is currently just an arbitrary non-empty string that can't match another manifest line. | |
| 134 | const manifest_header = "0"; | |
| 135 | pub const manifest_file_size_max = 100 * 1024 * 1024; | |
| 136 | ||
| 137 | 127 | /// The type used for hashing file contents. Currently, this is SipHash128(1, 3), because it |
| 138 | 128 | /// provides enough collision resistance for the Manifest use cases, while being one of our |
| 139 | 129 | /// fastest options right now. |
| ... | ... | @@ -143,56 +133,12 @@ pub const Hasher = crypto.auth.siphash.SipHash128(1, 3); |
| 143 | 133 | /// Refresh this with new random bytes when the manifest |
| 144 | 134 | /// format is modified in a non-backwards-compatible way. |
| 145 | 135 | pub const hasher_init: Hasher = Hasher.init(&.{ |
| 146 | 0x33, 0x52, 0xa2, 0x84, | |
| 147 | 0xcf, 0x17, 0x56, 0x57, | |
| 148 | 0x01, 0xbb, 0xcd, 0xe4, | |
| 149 | 0x77, 0xd6, 0xf0, 0x60, | |
| 136 | 0x02, 0xe9, 0xfa, 0xfe, | |
| 137 | 0xe0, 0x95, 0x81, 0x55, | |
| 138 | 0xf5, 0x5a, 0x15, 0xcb, | |
| 139 | 0x4a, 0xf4, 0x00, 0x09, | |
| 150 | 140 | }); |
| 151 | 141 | |
| 152 | pub 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 | ||
| 196 | 142 | pub const HashHelper = struct { |
| 197 | 143 | hasher: Hasher = hasher_init, |
| 198 | 144 | |
| ... | ... | @@ -299,7 +245,7 @@ pub const HashHelper = struct { |
| 299 | 245 | |
| 300 | 246 | pub fn binToHex(bin_digest: BinDigest) HexDigest { |
| 301 | 247 | var out_digest: HexDigest = undefined; |
| 302 | var w: std.Io.Writer = .fixed(&out_digest); | |
| 248 | var w: Io.Writer = .fixed(&out_digest); | |
| 303 | 249 | w.printHex(&bin_digest, .lower) catch unreachable; |
| 304 | 250 | return out_digest; |
| 305 | 251 | } |
| ... | ... | @@ -319,33 +265,337 @@ pub const Lock = struct { |
| 319 | 265 | } |
| 320 | 266 | }; |
| 321 | 267 | |
| 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. | |
| 322 | 271 | pub const Manifest = struct { |
| 323 | 272 | cache: *Cache, |
| 324 | 273 | /// Current state for incremental hashing. |
| 325 | 274 | hash: HashHelper, |
| 275 | /// When this is null, `Manifest` is in "pre-check" phase. Otherwise it is in "post-check" phase. | |
| 326 | 276 | manifest_file: ?Io.File, |
| 327 | manifest_dirty: bool, | |
| 328 | /// Set this flag to true before calling hit() in order to indicate that | |
| 329 | /// upon a cache hit, the code using the cache will not modify the files | |
| 330 | /// within the cache directory. This allows multiple processes to utilize | |
| 331 | /// the same cache directory at the same time. | |
| 277 | state: State, | |
| 278 | /// Set this flag to true before calling `check` in order to indicate that upon a cache hit, the code | |
| 279 | /// using the cache will not modify the files within the cache directory. This allows multiple processes | |
| 280 | /// to utilize the same cache directory at the same time. | |
| 332 | 281 | want_shared_lock: bool = true, |
| 333 | 282 | have_exclusive_lock: bool = false, |
| 334 | 283 | // Indicate that we want isProblematicTimestamp to perform a filesystem write in |
| 335 | 284 | // order to obtain a problematic timestamp for the next call. Calls after that |
| 336 | 285 | // will then use the same timestamp, to avoid unnecessary filesystem writes. |
| 337 | 286 | want_refresh_timestamp: bool = true, |
| 338 | files: Files = .{}, | |
| 339 | hex_digest: HexDigest, | |
| 340 | diagnostic: Diagnostic = .none, | |
| 287 | /// Uses `Cache.gpa`. | |
| 288 | files: Files = .empty, | |
| 289 | /// Indexes line up with `files`, but only up until `hit` is called. Uses | |
| 290 | /// `Cache.gpa`. | |
| 291 | input_paths: std.ArrayList(InputPath) = .empty, | |
| 341 | 292 | /// Keeps track of the last time we performed a file system write to observe |
| 342 | 293 | /// what time the file system thinks it is, according to its own granularity. |
| 343 | 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), | |
| 344 | 307 | |
| 345 | pub const Diagnostic = union(enum) { | |
| 346 | none, | |
| 308 | pub const Files = std.array_hash_map.Custom(File.Offset, void, File.HashContext, false); | |
| 309 | ||
| 310 | pub const State = enum { | |
| 311 | /// Call `addInputPath`, `addInputPathOptional`, and methods of `Manifest.hash`. Once all input files and state | |
| 312 | /// have been added, call `check`. | |
| 313 | input, | |
| 314 | /// `check` has already been called and a hit occurred. Call `hitDigest` to find cache artifact directory. | |
| 315 | hit, | |
| 316 | /// `hitDigest` has already been called. No further operations are allowed. | |
| 317 | hit_digested, | |
| 318 | /// `check` has already been called and a miss occurred. `missDigest` has not been called yet. Miss diagnostic | |
| 319 | /// data may still be accessed. Call `addDiscoveredPath` to add discovered files and directories. | |
| 320 | miss, | |
| 321 | /// A miss occurred, `missDigest` has not been called yet. Call `addDiscoveredPath` to add more discovered files | |
| 322 | /// and directories. Miss diagnostic data may no longer be accessed. | |
| 323 | miss_discovered, | |
| 324 | /// `missDigest` has been called, but `finalize` has not been called yet. | |
| 325 | miss_digested, | |
| 326 | /// The cache manifest has been written to disk such that the next check for the same inputs will yield a hit. | |
| 327 | /// No further operations are allowed. | |
| 328 | miss_finalized, | |
| 329 | }; | |
| 330 | ||
| 331 | /// Source files and directories whose prefix and relative path are | |
| 332 | /// included when computing the cache manifest digest. It's the information | |
| 333 | /// needed to lazily hash the input files only when a cache miss occurs. | |
| 334 | /// | |
| 335 | /// `File.prefix`, `File.path`, and `File.mode` will be always populated, | |
| 336 | /// but the other fields of `File` will be populated depending on the | |
| 337 | /// fields of `InputPath`. | |
| 338 | pub const InputPath = struct { | |
| 339 | /// Determines whether `check` will keep the handle open or close it. | |
| 340 | request_handle: bool, | |
| 341 | /// Determines whether `handle` is populated. | |
| 342 | have_handle: bool, | |
| 343 | /// Determines whether `File.size`, `File.inode`, and `File.mtime` are populated. | |
| 344 | have_stat: bool, | |
| 345 | /// Determines whether `File.digest` is populated. | |
| 346 | have_digest: bool, | |
| 347 | contents: struct { | |
| 348 | pop_off: enum(u32) { unpopulated = std.math.maxInt(u32), _ }, | |
| 349 | req_len: enum(u32) { unrequested = std.math.maxInt(u32) - 1, requested = std.math.maxInt(u32), _ }, | |
| 350 | ||
| 351 | pub const requested: @This() = .{ | |
| 352 | .pop_off = .unpopulated, | |
| 353 | .req_len = .requested, | |
| 354 | }; | |
| 355 | ||
| 356 | pub const unrequested: @This() = .{ | |
| 357 | .pop_off = .unpopulated, | |
| 358 | .req_len = .unrequested, | |
| 359 | }; | |
| 360 | ||
| 361 | pub fn populated(p: Populated) @This() { | |
| 362 | return .{ | |
| 363 | .pop_off = @fromBackingInt(p.off), | |
| 364 | .req_len = @fromBackingInt(p.len), | |
| 365 | }; | |
| 366 | } | |
| 367 | ||
| 368 | pub const Unwrapped = union(enum) { | |
| 369 | unrequested, | |
| 370 | requested, | |
| 371 | populated: Populated, | |
| 372 | }; | |
| 373 | ||
| 374 | pub const Populated = struct { | |
| 375 | off: u32, | |
| 376 | len: u32, | |
| 377 | }; | |
| 378 | ||
| 379 | pub fn unwrap(this: @This()) Unwrapped { | |
| 380 | return switch (this.pop_off) { | |
| 381 | .unpopulated => switch (this.req_len) { | |
| 382 | .unrequested => .unrequested, | |
| 383 | .requested => .requested, | |
| 384 | _ => unreachable, | |
| 385 | }, | |
| 386 | _ => .{ .populated = .{ | |
| 387 | .off = @backingInt(this.pop_off), | |
| 388 | .len = @backingInt(this.req_len), | |
| 389 | } }, | |
| 390 | }; | |
| 391 | } | |
| 392 | ||
| 393 | pub fn get(this: @This(), all_contents: []const u8) ?[]const u8 { | |
| 394 | return switch (this.unwrap()) { | |
| 395 | .unrequested, .requested => null, | |
| 396 | .populated => |p| all_contents[p.off..][0..p.len], | |
| 397 | }; | |
| 398 | } | |
| 399 | }, | |
| 400 | /// `have_handle` determines whether this is populated. | |
| 401 | handle: union { | |
| 402 | file: Io.File, | |
| 403 | dir: Io.Dir, | |
| 404 | }, | |
| 405 | ||
| 406 | pub const Handle = union(enum) { | |
| 407 | none, | |
| 408 | file: Io.File, | |
| 409 | dir: Io.Dir, | |
| 410 | }; | |
| 411 | ||
| 412 | /// Index into `Manifest.input_paths`. | |
| 413 | pub const Index = enum(u32) { | |
| 414 | _, | |
| 415 | ||
| 416 | pub fn get(i: @This(), manifest: *const Manifest) InputPath { | |
| 417 | return manifest.input_paths.items[@backingInt(i)]; | |
| 418 | } | |
| 419 | ||
| 420 | pub fn offset(i: @This(), manifest: *const Manifest) File.Offset { | |
| 421 | return manifest.files.keys()[@backingInt(i)]; | |
| 422 | } | |
| 423 | ||
| 424 | pub fn contents(i: @This(), manifest: *const Manifest) []const u8 { | |
| 425 | const input_path = get(i, manifest); | |
| 426 | return input_path.contents.get(manifest.all_input_content.items).?; | |
| 427 | } | |
| 428 | }; | |
| 429 | ||
| 430 | pub fn getHandle(this: @This(), file: *const File) Handle { | |
| 431 | return if (!this.have_handle) .none else switch (file.flags.is_directory) { | |
| 432 | false => .{ .file = this.handle.file }, | |
| 433 | true => .{ .dir = this.handle.dir }, | |
| 434 | }; | |
| 435 | } | |
| 436 | }; | |
| 437 | ||
| 438 | /// The data per tracked input file that is stored in the manifest file. | |
| 439 | pub const File = extern struct { | |
| 440 | size: u64 align(1), | |
| 441 | inode: Io.File.INode align(1), | |
| 442 | /// Nanoseconds. | |
| 443 | mtime: i64 align(1), | |
| 444 | /// To simplify the hashing logic, this value is computed from size, inode, and mtime | |
| 445 | /// in `hashFromMetadata` in the case that `Flags.metadata_only` is `true`. | |
| 446 | digest: BinDigest align(1), | |
| 447 | /// Starting with this field and continuing into the path, excluding the null byte, | |
| 448 | /// is the string that is hashed for the manifest digest. | |
| 449 | flags: Flags align(1), | |
| 450 | /// Terminated by zero byte, then followed by padding until 8-byte aligned. | |
| 451 | path_start: [0]u8 align(1), | |
| 452 | ||
| 453 | pub const Flags = packed struct(u8) { | |
| 454 | is_directory: bool, | |
| 455 | metadata_only: bool, | |
| 456 | prefix: PrefixIndex, | |
| 457 | }; | |
| 458 | ||
| 459 | /// Prefixes path names in encoded directory contents. Starts numbering | |
| 460 | /// at `1` so that null byte can be used unambiguously as entry | |
| 461 | /// separator. | |
| 462 | pub const Kind = enum(u8) { | |
| 463 | file = 1, | |
| 464 | directory = 2, | |
| 465 | other = 3, | |
| 466 | ||
| 467 | pub fn fromStat(kind: Io.File.Kind) @This() { | |
| 468 | return switch (kind) { | |
| 469 | .file => .file, | |
| 470 | .directory => .directory, | |
| 471 | else => .other, | |
| 472 | }; | |
| 473 | } | |
| 474 | }; | |
| 475 | ||
| 476 | /// Byte index within `Manifest.contents` where the entry starts. | |
| 477 | pub const Offset = enum(u32) { | |
| 478 | _, | |
| 479 | ||
| 480 | pub fn get(offset: Offset, contents: []u8) *File { | |
| 481 | return @constCast(getConst(offset, contents)); | |
| 482 | } | |
| 483 | ||
| 484 | pub fn getConst(offset: Offset, contents: []const u8) *const File { | |
| 485 | return @ptrCast(@alignCast(contents[@backingInt(offset)..][0..@sizeOf(File)])); | |
| 486 | } | |
| 487 | ||
| 488 | pub fn getFallible(offset: Offset, contents: []u8) error{InvalidFormat}!*File { | |
| 489 | // TODO make @constCast support in-memory coercion across error unions and optionals | |
| 490 | return @constCast(try getFallibleConst(offset, contents)); | |
| 491 | } | |
| 492 | ||
| 493 | pub fn getFallibleConst(offset: Offset, contents: []const u8) error{InvalidFormat}!*const File { | |
| 494 | if (@backingInt(offset) + @sizeOf(File) >= contents.len) return error.InvalidFormat; | |
| 495 | if (!mem.isAligned(@backingInt(offset), @alignOf(File))) return error.InvalidFormat; | |
| 496 | return getConst(offset, contents); | |
| 497 | } | |
| 498 | ||
| 499 | pub fn flagsAndPath(off: File.Offset, contents: []const u8) [:0]const u8 { | |
| 500 | const flags_off = @offsetOf(File, "flags"); | |
| 501 | comptime assert(@offsetOf(File, "path_start") - flags_off == 1); | |
| 502 | const start = @backingInt(off) + flags_off; | |
| 503 | // Scan for the sentinel starting from the path_start offset because flags might be zero. | |
| 504 | const end = mem.findScalarPos(u8, contents, start + 1, 0).?; | |
| 505 | return contents[start..end :0]; | |
| 506 | } | |
| 507 | ||
| 508 | pub fn pathFallible(off: File.Offset, contents: []const u8) error{InvalidFormat}![:0]const u8 { | |
| 509 | const path_start = @backingInt(off) + @offsetOf(File, "path_start"); | |
| 510 | const path_end = mem.findScalarPos(u8, contents, path_start, 0) orelse return error.InvalidFormat; | |
| 511 | return contents[path_start..path_end :0]; | |
| 512 | } | |
| 513 | ||
| 514 | pub fn path(off: File.Offset, contents: []const u8) [:0]const u8 { | |
| 515 | return pathFallible(off, contents) catch unreachable; | |
| 516 | } | |
| 517 | }; | |
| 518 | ||
| 519 | /// Intentionally matches if the files are different only by flags other than prefix. | |
| 520 | pub const HashContext = struct { | |
| 521 | contents: []const u8, | |
| 522 | ||
| 523 | pub fn hash(this: @This(), off: Offset) u32 { | |
| 524 | const file_prefix = off.getConst(this.contents).flags.prefix; | |
| 525 | const file_path = off.path(this.contents); | |
| 526 | return @truncate(std.hash.Wyhash.hash(file_prefix, file_path)); | |
| 527 | } | |
| 528 | ||
| 529 | pub fn eql(this: @This(), a_off: Offset, b_off: Offset, b_index: usize) bool { | |
| 530 | _ = b_index; | |
| 531 | const a_prefix = a_off.getConst(this.contents).flags.prefix; | |
| 532 | const b_prefix = b_off.getConst(this.contents).flags.prefix; | |
| 533 | if (a_prefix != b_prefix) return false; | |
| 534 | const a_path = a_off.path(this.contents); | |
| 535 | const b_path = b_off.path(this.contents); | |
| 536 | return mem.eql(u8, a_path, b_path); | |
| 537 | } | |
| 538 | }; | |
| 539 | ||
| 540 | fn hashFromMetadata(file: *File) void { | |
| 541 | var hasher = hasher_init; | |
| 542 | hasher.update(mem.asBytes(&file.size)); | |
| 543 | hasher.update(mem.asBytes(&file.inode)); | |
| 544 | hasher.update(mem.asBytes(&file.mtime)); | |
| 545 | hasher.final(&file.digest); | |
| 546 | } | |
| 547 | ||
| 548 | fn stat(file: *const File) Stat { | |
| 549 | return .{ | |
| 550 | .size = file.size, | |
| 551 | .inode = file.inode, | |
| 552 | .mtime = .fromNanoseconds(file.mtime), | |
| 553 | }; | |
| 554 | } | |
| 555 | ||
| 556 | fn setStatUnchecked(file: *File, s: Stat) void { | |
| 557 | file.size = s.size; | |
| 558 | file.inode = s.inode; | |
| 559 | file.mtime = @intCast(s.mtime.toNanoseconds()); | |
| 560 | } | |
| 561 | ||
| 562 | fn setStat(file: *File, m: *Manifest, s: Stat) Io.Cancelable!void { | |
| 563 | setStatUnchecked(file, s); | |
| 564 | ||
| 565 | if (try m.isProblematicTimestamp(s.mtime)) { | |
| 566 | // The actual file has an unreliable timestamp; force it to be hashed. | |
| 567 | file.mtime = 0; | |
| 568 | file.inode = 0; | |
| 569 | } | |
| 570 | } | |
| 571 | ||
| 572 | /// Returns true if the stat was changed. Updates the `file` with the new stat value. | |
| 573 | fn setStatChanged(file: *File, m: *Manifest, s: Stat) Io.Cancelable!bool { | |
| 574 | if (s.size == file.size and | |
| 575 | s.mtime.nanoseconds == file.mtime and | |
| 576 | s.inode == file.inode) | |
| 577 | { | |
| 578 | return false; | |
| 579 | } else { | |
| 580 | try setStat(file, m, s); | |
| 581 | return true; | |
| 582 | } | |
| 583 | } | |
| 584 | ||
| 585 | /// `path_len` does not include the null byte. | |
| 586 | pub fn sizeOf(path_len: usize) usize { | |
| 587 | const end = @offsetOf(File, "path_start") + path_len; | |
| 588 | const needed_alignment = @alignOf(File) - (end % @alignOf(File)); | |
| 589 | assert(needed_alignment >= 1); // Always need at least a null byte. | |
| 590 | return end + needed_alignment; | |
| 591 | } | |
| 592 | }; | |
| 593 | ||
| 594 | pub const CheckDiagnostic = union(enum) { | |
| 347 | 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 | 599 | manifest_lock: Io.File.LockError, |
| 350 | 600 | file_open: FileOp, |
| 351 | 601 | file_stat: FileOp, |
| ... | ... | @@ -353,115 +603,274 @@ pub const Manifest = struct { |
| 353 | 603 | file_hash: FileOp, |
| 354 | 604 | |
| 355 | 605 | pub const FileOp = struct { |
| 356 | file_index: usize, | |
| 606 | file_offset: File.Offset, | |
| 357 | 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 | }; | |
| 360 | 619 | |
| 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 | }; | |
| 362 | 637 | |
| 363 | pub const FilesContext = struct { | |
| 364 | pub fn hash(fc: FilesContext, file: File) u32 { | |
| 365 | _ = fc; | |
| 366 | return file.prefixed_path.hash(); | |
| 638 | pub fn fmt(diagnostic: CheckDiagnostic, manifest: *const Manifest) Format { | |
| 639 | return .{ | |
| 640 | .diagnostic = diagnostic, | |
| 641 | .manifest = manifest, | |
| 642 | }; | |
| 367 | 643 | } |
| 644 | }; | |
| 645 | ||
| 646 | pub const Stat = struct { | |
| 647 | size: u64, | |
| 648 | inode: Io.File.INode, | |
| 649 | mtime: Io.Timestamp, | |
| 368 | 650 | |
| 369 | pub fn eql(fc: FilesContext, a: File, b: File, b_index: usize) bool { | |
| 370 | _ = fc; | |
| 371 | _ = b_index; | |
| 372 | return a.prefixed_path.eql(b.prefixed_path); | |
| 651 | pub fn init(other: Io.File.Stat) Stat { | |
| 652 | return .{ | |
| 653 | .size = other.size, | |
| 654 | .inode = other.inode, | |
| 655 | .mtime = other.mtime, | |
| 656 | }; | |
| 373 | 657 | } |
| 374 | 658 | }; |
| 375 | 659 | |
| 376 | const FilesAdapter = struct { | |
| 377 | pub fn eql(context: @This(), a: PrefixedPath, b: File, b_index: usize) bool { | |
| 378 | _ = context; | |
| 379 | _ = b_index; | |
| 380 | return a.eql(b.prefixed_path); | |
| 660 | pub const PathHandle = union(enum) { | |
| 661 | file: ?Io.File, | |
| 662 | /// If provided, this handle must be opened with iteration capability. | |
| 663 | dir: ?Io.Dir, | |
| 664 | ||
| 665 | pub fn isDirectory(this: @This()) bool { | |
| 666 | return this == .dir; | |
| 381 | 667 | } |
| 382 | 668 | |
| 383 | pub fn hash(context: @This(), key: PrefixedPath) u32 { | |
| 384 | _ = context; | |
| 385 | return key.hash(); | |
| 669 | pub fn have(this: @This()) bool { | |
| 670 | return switch (this) { | |
| 671 | .file => |opt_file| opt_file != null, | |
| 672 | .dir => |opt_dir| opt_dir != null, | |
| 673 | }; | |
| 386 | 674 | } |
| 387 | 675 | }; |
| 388 | 676 | |
| 389 | /// Add a file as a dependency of process being cached. When `hit` is | |
| 390 | /// called, the file's contents will be checked to ensure that it matches | |
| 391 | /// the contents from previous times. | |
| 677 | pub const AddInputPathOptions = struct { | |
| 678 | /// If provided, will be closed when `check` is called, unless `request_handle` is also set. | |
| 679 | handle: PathHandle = .{ .file = null }, | |
| 680 | stat: ?Stat = null, | |
| 681 | /// If set, file handle will remain open after `check` is called. | |
| 682 | request_handle: bool = false, | |
| 683 | /// Can request file or directory contents depending on `handle`. | |
| 684 | request_contents: bool = false, | |
| 685 | /// Content hashing skipped; any difference in metadata implies cache | |
| 686 | /// miss. | |
| 687 | metadata_only: bool = false, | |
| 688 | }; | |
| 689 | ||
| 690 | /// Add a file or directory path as a dependency of process being cached. | |
| 691 | /// When `hit` is called, the contents will be checked to ensure | |
| 692 | /// that it matches the contents from previous times. | |
| 392 | 693 | /// |
| 393 | /// Max file size will be used to determine the amount of space the file contents | |
| 394 | /// are allowed to take up in memory. If max_file_size is null, then the contents | |
| 395 | /// will not be loaded into memory. | |
| 694 | /// The contents of the input file may be requested and subsequently | |
| 695 | /// obtained via methods of the returned `InputPath.Index` after calling | |
| 696 | /// `hit`. | |
| 396 | 697 | /// |
| 397 | /// Returns the index of the entry in the `files` array list. You can use it | |
| 398 | /// to access the contents of the file after calling `hit()` like so: | |
| 698 | /// Contents of a directory are considered to be the sorted list of file | |
| 699 | /// names of direct entries, separated by null byte. Each file name is | |
| 700 | /// prefixed by `Io.File.Kind` byte, +1 so that the zero tag is not aliased | |
| 701 | /// by the entry separator. | |
| 399 | 702 | /// |
| 400 | /// ``` | |
| 401 | /// var file_contents = cache_hash.files.keys()[file_index].contents.?; | |
| 402 | /// ``` | |
| 403 | pub fn addFilePath(m: *Manifest, file_path: Path, max_file_size: ?usize) !usize { | |
| 404 | return addOpenedFile(m, file_path, null, max_file_size); | |
| 703 | /// See also: | |
| 704 | /// * `addDiscoveredPath` | |
| 705 | pub fn addInputPath(m: *Manifest, path: Path, options: AddInputPathOptions) Allocator.Error!InputPath.Index { | |
| 706 | assert(m.state == .input); | |
| 707 | const cache = m.cache; | |
| 708 | const gpa = cache.gpa; | |
| 709 | const is_directory = options.handle.isDirectory(); | |
| 710 | ||
| 711 | try m.files.ensureUnusedCapacityContext(gpa, 1, .{ .contents = m.contents.items }); | |
| 712 | try m.input_paths.ensureUnusedCapacity(gpa, 1); | |
| 713 | ||
| 714 | const new_file_offset: File.Offset = @fromBackingInt(@intCast(m.contents.items.len)); | |
| 715 | try m.contents.appendNTimes(gpa, 0, @offsetOf(File, "path_start")); | |
| 716 | errdefer m.contents.shrinkRetainingCapacity(@backingInt(new_file_offset)); | |
| 717 | ||
| 718 | const new_prefix = try cache.resolveAppendPath(&m.contents, path); | |
| 719 | assert(mem.isAligned(m.contents.items.len, @alignOf(File))); | |
| 720 | new_file_offset.get(m.contents.items).flags = .{ | |
| 721 | .prefix = new_prefix, | |
| 722 | .is_directory = is_directory, | |
| 723 | .metadata_only = options.metadata_only, | |
| 724 | }; | |
| 725 | ||
| 726 | const gop = m.files.getOrPutAssumeCapacityContext(new_file_offset, .{ .contents = m.contents.items }); | |
| 727 | m.files.lockPointers(); | |
| 728 | defer m.files.unlockPointers(); | |
| 729 | ||
| 730 | if (gop.found_existing) { | |
| 731 | m.contents.shrinkRetainingCapacity(@backingInt(new_file_offset)); | |
| 732 | const existing_input_file = &m.input_paths.items[gop.index]; | |
| 733 | switch (options.handle) { | |
| 734 | .file => |opt_file| if (opt_file) |file| { | |
| 735 | existing_input_file.handle = .{ .file = file }; | |
| 736 | existing_input_file.have_handle = true; | |
| 737 | }, | |
| 738 | .dir => |opt_dir| if (opt_dir) |dir| { | |
| 739 | existing_input_file.handle = .{ .dir = dir }; | |
| 740 | existing_input_file.have_handle = true; | |
| 741 | }, | |
| 742 | } | |
| 743 | if (options.request_contents) switch (existing_input_file.contents.unwrap()) { | |
| 744 | .requested, .unrequested => existing_input_file.contents = .requested, | |
| 745 | .populated => {}, | |
| 746 | }; | |
| 747 | const existing_header = m.files.keys()[gop.index].get(m.contents.items); | |
| 748 | if (options.stat) |stat| { | |
| 749 | existing_input_file.have_stat = true; | |
| 750 | existing_header.size = stat.size; | |
| 751 | existing_header.inode = stat.inode; | |
| 752 | existing_header.mtime = @intCast(stat.mtime.toNanoseconds()); | |
| 753 | } | |
| 754 | // If it trips, the same file path has been added to the cache | |
| 755 | // manifest both as a directory and as a normal file, making the | |
| 756 | // intended caching behavior ambiguous. | |
| 757 | assert(existing_header.flags.is_directory == is_directory); | |
| 758 | if (!options.metadata_only) | |
| 759 | existing_header.flags.metadata_only = false; | |
| 760 | } else { | |
| 761 | m.input_paths.appendAssumeCapacity(.{ | |
| 762 | .request_handle = options.request_handle, | |
| 763 | .have_handle = options.handle.have(), | |
| 764 | .handle = switch (options.handle) { | |
| 765 | .file => |opt_file| if (opt_file) |file| .{ .file = file } else undefined, | |
| 766 | .dir => |opt_dir| if (opt_dir) |dir| .{ .dir = dir } else undefined, | |
| 767 | }, | |
| 768 | .contents = if (options.request_contents) .requested else .unrequested, | |
| 769 | .have_digest = false, | |
| 770 | .have_stat = options.stat != null, | |
| 771 | }); | |
| 772 | assert(m.input_paths.items.len - 1 == gop.index); | |
| 773 | if (options.stat) |stat| { | |
| 774 | const header = new_file_offset.get(m.contents.items); | |
| 775 | header.size = stat.size; | |
| 776 | header.inode = stat.inode; | |
| 777 | header.mtime = @intCast(stat.mtime.toNanoseconds()); | |
| 778 | } | |
| 779 | } | |
| 780 | return @fromBackingInt(@intCast(gop.index)); | |
| 405 | 781 | } |
| 406 | 782 | |
| 407 | /// Same as `addFilePath` except the file has already been opened. | |
| 408 | pub fn addOpenedFile(m: *Manifest, path: Path, handle: ?Io.File, max_file_size: ?usize) !usize { | |
| 409 | const gpa = m.cache.gpa; | |
| 410 | try m.files.ensureUnusedCapacity(gpa, 1); | |
| 411 | const resolved_path = try std.fs.path.resolve(gpa, &.{ | |
| 412 | path.root_dir.path orelse ".", | |
| 413 | path.subPathOrDot(), | |
| 414 | }); | |
| 415 | errdefer gpa.free(resolved_path); | |
| 416 | const prefixed_path = try m.cache.findPrefixResolved(resolved_path); | |
| 417 | return addFileInner(m, prefixed_path, handle, max_file_size); | |
| 783 | pub fn addInputPathOptional(m: *Manifest, opt_path: ?Path, options: AddInputPathOptions) Allocator.Error!void { | |
| 784 | assert(m.state == .input); | |
| 785 | m.hash.add(opt_path != null); | |
| 786 | _ = try addInputPath(m, opt_path orelse return, options); | |
| 418 | 787 | } |
| 419 | 788 | |
| 420 | fn addFileInner(self: *Manifest, prefixed_path: PrefixedPath, handle: ?Io.File, max_file_size: ?usize) usize { | |
| 421 | const gop = self.files.getOrPutAssumeCapacityAdapted(prefixed_path, FilesAdapter{}); | |
| 422 | if (gop.found_existing) { | |
| 423 | self.cache.gpa.free(prefixed_path.sub_path); | |
| 424 | gop.key_ptr.updateMaxSize(max_file_size); | |
| 425 | gop.key_ptr.updateHandle(handle); | |
| 426 | return gop.index; | |
| 427 | } | |
| 428 | gop.key_ptr.* = .{ | |
| 429 | .prefixed_path = prefixed_path, | |
| 430 | .contents = null, | |
| 431 | .max_file_size = max_file_size, | |
| 432 | .stat = undefined, | |
| 433 | .bin_digest = undefined, | |
| 434 | .handle = handle, | |
| 789 | pub const AddInputDepFileError = error{ | |
| 790 | InvalidDepFile, | |
| 791 | } || Allocator.Error || Io.File.OpenError || Io.File.Reader.Error; | |
| 792 | ||
| 793 | pub fn addInputDepFile(m: *Manifest, path: Path, diagnostic: ?*DepTokenizer.Token) AddInputDepFileError!void { | |
| 794 | assert(m.manifest_file == null); | |
| 795 | assert(m.state == .input); | |
| 796 | ||
| 797 | const cache = m.cache; | |
| 798 | const gpa = cache.gpa; | |
| 799 | const io = cache.io; | |
| 800 | ||
| 801 | // TODO: change DepTokenizer to be streaming rather than operating on slice of bytes. | |
| 802 | const prev_len = m.all_input_content.items.len; | |
| 803 | defer m.all_input_content.items.len = prev_len; | |
| 804 | ||
| 805 | const file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); | |
| 806 | defer file.close(io); | |
| 807 | ||
| 808 | var file_reader: Io.File.Reader = .init(file, io, &.{}); | |
| 809 | file_reader.interface.appendRemainingUnlimited(gpa, &m.all_input_content) catch |err| switch (err) { | |
| 810 | error.OutOfMemory => |e| return e, | |
| 811 | error.ReadFailed => return file_reader.err.?, | |
| 435 | 812 | }; |
| 436 | 813 | |
| 437 | self.hash.add(prefixed_path.prefix); | |
| 438 | self.hash.addBytes(prefixed_path.sub_path); | |
| 814 | var aux: std.ArrayList(u8) = .empty; | |
| 815 | defer aux.deinit(gpa); | |
| 439 | 816 | |
| 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 | } |
| 442 | 834 | |
| 443 | pub fn addOptionalFilePath(self: *Manifest, optional_file_path: ?Path) !void { | |
| 444 | self.hash.add(optional_file_path != null); | |
| 445 | const file_path = optional_file_path orelse return; | |
| 446 | _ = try self.addFilePath(file_path, null); | |
| 447 | } | |
| 835 | pub const CheckResult = union(enum) { | |
| 836 | hit, | |
| 837 | incomplete_manifest, | |
| 838 | invalid_manifest, | |
| 839 | path_deleted: File.Offset, | |
| 840 | directory_status_changed: File.Offset, | |
| 841 | metadata_changed: File.Offset, | |
| 842 | contents_changed: File.Offset, | |
| 843 | ||
| 844 | pub const Format = struct { | |
| 845 | check_result: CheckResult, | |
| 846 | manifest: *const Manifest, | |
| 847 | ||
| 848 | pub fn format(this: @This(), w: *Io.Writer) Io.Writer.Error!void { | |
| 849 | switch (this.check_result) { | |
| 850 | .hit, .incomplete_manifest, .invalid_manifest => return w.writeAll(@tagName(this.check_result)), | |
| 851 | .path_deleted, .directory_status_changed, .metadata_changed, .contents_changed => |off| { | |
| 852 | const path = off.path(this.manifest.contents.items); | |
| 853 | return w.print("{t} {s}", .{ this.check_result, path }); | |
| 854 | }, | |
| 855 | } | |
| 856 | } | |
| 857 | }; | |
| 448 | 858 | |
| 449 | pub fn addDepFile(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void { | |
| 450 | assert(self.manifest_file == null); | |
| 451 | return self.addDepFileMaybePost(dir, dep_file_sub_path); | |
| 452 | } | |
| 859 | pub fn fmt(check_result: CheckResult, manifest: *const Manifest) Format { | |
| 860 | return .{ | |
| 861 | .check_result = check_result, | |
| 862 | .manifest = manifest, | |
| 863 | }; | |
| 864 | } | |
| 865 | }; | |
| 453 | 866 | |
| 454 | pub const HitError = error{ | |
| 867 | pub const CheckError = error{ | |
| 455 | 868 | /// Unable to check the cache for a reason that has been recorded into |
| 456 | 869 | /// the `diagnostic` field. |
| 457 | 870 | CacheCheckFailed, |
| 458 | /// A cache manifest file exists however it could not be parsed. | |
| 459 | InvalidFormat, | |
| 460 | OutOfMemory, | |
| 461 | Canceled, | |
| 462 | }; | |
| 871 | } || Allocator.Error || Io.Cancelable; | |
| 463 | 872 | |
| 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 | 874 | /// A hex encoding of its hash is available by calling `final`. |
| 466 | 875 | /// |
| 467 | 876 | /// This function will also acquire an exclusive lock to the manifest file. This means |
| ... | ... | @@ -473,52 +882,53 @@ pub const Manifest = struct { |
| 473 | 882 | /// The lock on the manifest file is released when `deinit` is called. As another |
| 474 | 883 | /// option, one may call `toOwnedLock` to obtain a smaller object which can represent |
| 475 | 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 | 890 | const node = parent_progress_node.start("Reusing Cache Artifacts", 0); |
| 478 | 891 | defer node.end(); |
| 479 | return hitInner(man); | |
| 892 | return checkProgressless(man, diag); | |
| 480 | 893 | } |
| 481 | 894 | |
| 482 | pub fn hitInner(self: *Manifest) HitError!bool { | |
| 483 | assert(self.manifest_file == null); | |
| 484 | ||
| 485 | self.diagnostic = .none; | |
| 486 | ||
| 487 | const ext = ".txt"; | |
| 488 | var manifest_file_path: [hex_digest_len + ext.len]u8 = undefined; | |
| 489 | ||
| 490 | var bin_digest: BinDigest = undefined; | |
| 491 | self.hash.hasher.final(&bin_digest); | |
| 895 | pub fn checkProgressless(man: *Manifest, diag: *CheckDiagnostic) CheckError!CheckResult { | |
| 896 | assert(man.state == .input); | |
| 897 | assert(man.manifest_file == null); | |
| 492 | 898 | |
| 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 | } | |
| 494 | 905 | |
| 495 | @memcpy(manifest_file_path[0..self.hex_digest.len], &self.hex_digest); | |
| 496 | manifest_file_path[hex_digest_len..][0..ext.len].* = ext.*; | |
| 906 | var input_digest: BinDigest = undefined; | |
| 907 | man.hash.hasher.final(&input_digest); | |
| 908 | const input_hex_digest = binToHex(input_digest); | |
| 497 | 909 | |
| 498 | const io = self.cache.io; | |
| 910 | const manifest_file_path = &input_hex_digest; | |
| 911 | const io = man.cache.io; | |
| 499 | 912 | |
| 500 | 913 | // We'll try to open the cache with an exclusive lock, but if that would block |
| 501 | 914 | // and `want_shared_lock` is set, a shared lock might be sufficient, so we'll |
| 502 | 915 | // open with a shared lock instead. |
| 503 | 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 | 918 | .read = true, |
| 506 | 919 | .truncate = false, |
| 507 | 920 | .lock = .exclusive, |
| 508 | .lock_nonblocking = self.want_shared_lock, | |
| 921 | .lock_nonblocking = man.want_shared_lock, | |
| 509 | 922 | })) |manifest_file| { |
| 510 | self.manifest_file = manifest_file; | |
| 511 | self.have_exclusive_lock = true; | |
| 923 | man.manifest_file = manifest_file; | |
| 924 | man.have_exclusive_lock = true; | |
| 512 | 925 | break; |
| 513 | 926 | } else |err| switch (err) { |
| 514 | 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 | 929 | .mode = .read_write, |
| 517 | 930 | .lock = .shared, |
| 518 | }) catch |e| { | |
| 519 | self.diagnostic = .{ .manifest_create = e }; | |
| 520 | return error.CacheCheckFailed; | |
| 521 | }; | |
| 931 | }) catch |e| return fail(diag, .{ .manifest_create = e }); | |
| 522 | 932 | break; |
| 523 | 933 | }, |
| 524 | 934 | error.FileNotFound => { |
| ... | ... | @@ -531,317 +941,699 @@ pub const Manifest = struct { |
| 531 | 941 | // disambiguates by returning EEXIST, indicating original |
| 532 | 942 | // failure was a race, or ENOENT, indicating deletion of |
| 533 | 943 | // the directory of our open handle. |
| 534 | if (!builtin.os.tag.isDarwin()) { | |
| 535 | self.diagnostic = .{ .manifest_create = error.FileNotFound }; | |
| 536 | return error.CacheCheckFailed; | |
| 537 | } | |
| 944 | if (!builtin.os.tag.isDarwin()) return fail(diag, .{ .manifest_create = error.FileNotFound }); | |
| 538 | 945 | |
| 539 | if (self.cache.manifest_dir.createFile(io, &manifest_file_path, .{ | |
| 946 | if (man.cache.manifest_dir.createFile(io, manifest_file_path, .{ | |
| 540 | 947 | .read = true, |
| 541 | 948 | .truncate = false, |
| 542 | 949 | .lock = .exclusive, |
| 543 | .lock_nonblocking = self.want_shared_lock, | |
| 950 | .lock_nonblocking = man.want_shared_lock, | |
| 544 | 951 | .exclusive = true, |
| 545 | 952 | })) |manifest_file| { |
| 546 | self.manifest_file = manifest_file; | |
| 547 | self.have_exclusive_lock = true; | |
| 953 | man.manifest_file = manifest_file; | |
| 954 | man.have_exclusive_lock = true; | |
| 548 | 955 | break; |
| 549 | 956 | } else |excl_err| switch (excl_err) { |
| 550 | 957 | error.WouldBlock, error.PathAlreadyExists => continue, |
| 551 | error.FileNotFound => { | |
| 552 | self.diagnostic = .{ .manifest_create = error.FileNotFound }; | |
| 553 | return error.CacheCheckFailed; | |
| 554 | }, | |
| 958 | error.FileNotFound => return fail(diag, .{ .manifest_create = error.FileNotFound }), | |
| 555 | 959 | error.Canceled => |e| return e, |
| 556 | else => |e| { | |
| 557 | self.diagnostic = .{ .manifest_create = e }; | |
| 558 | return error.CacheCheckFailed; | |
| 559 | }, | |
| 960 | else => |e| return fail(diag, .{ .manifest_create = e }), | |
| 560 | 961 | } |
| 561 | 962 | }, |
| 562 | error.Canceled => |e| return e, | |
| 563 | else => |e| { | |
| 564 | self.diagnostic = .{ .manifest_create = e }; | |
| 565 | return error.CacheCheckFailed; | |
| 963 | error.Canceled => |e| return e, | |
| 964 | else => |e| return fail(diag, .{ .manifest_create = e }), | |
| 965 | } | |
| 966 | } | |
| 967 | ||
| 968 | man.want_refresh_timestamp = true; | |
| 969 | ||
| 970 | // We're going to construct a second hash. Its input will begin with the digest we've already computed | |
| 971 | // (`input_digest`), and then it'll have the digests of each input file, including discovered files (see | |
| 972 | // `addDiscoveredPath`). If this is a hit, we learn the set of discovered files from the manifest on disk. If | |
| 973 | // this is a miss, we'll learn those from future calls to `addDiscoveredPath` etc. As such, the state of | |
| 974 | // `man.hash.hasher` after this function depends on whether this is a hit or a miss. | |
| 975 | // | |
| 976 | // If we return `CacheStatus.hit`, then `man.hash.hasher` must already include the digests of the discovered | |
| 977 | // files, so the caller can call `final`. Otherwise, on a cache miss, `man.hash.hasher` will include the digests | |
| 978 | // of all non-discovered files -- that is, the ones we've already been told about. The rest will be discovered | |
| 979 | // through calls to `addDiscoveredPath` etc, which will update the hasher. After all files are added, the user | |
| 980 | // can use `final`, and will at some point `finalize` the file list to disk. | |
| 981 | man.hash.hasher = hasher_init; | |
| 982 | man.hash.hasher.update(&input_digest); | |
| 983 | ||
| 984 | hit: { | |
| 985 | const miss_result = miss: { | |
| 986 | const result = try man.checkLocked(diag); | |
| 987 | if (result == .hit) { | |
| 988 | break :hit; | |
| 989 | } else if (!try man.upgradeToExclusiveLock(diag)) { | |
| 990 | break :miss result; | |
| 991 | } | |
| 992 | // Missed with the shared lock, and upgraded to an exclusive lock. However, another process may have | |
| 993 | // modified the cache directory, so we need to check again before deciding to miss. | |
| 994 | man.shrinkFilesToInput(); | |
| 995 | const refreshed_result = try man.checkLocked(diag); | |
| 996 | if (refreshed_result == .hit) break :hit; | |
| 997 | break :miss refreshed_result; | |
| 998 | }; | |
| 999 | ||
| 1000 | // Cache miss, but `checkLocked` guarantees that all input files have their digests computed, even on a | |
| 1001 | // cache miss, which is needed because they will be used in the manifest digest. | |
| 1002 | man.state = .miss; | |
| 1003 | return miss_result; | |
| 1004 | } | |
| 1005 | ||
| 1006 | if (man.want_shared_lock) { | |
| 1007 | man.downgradeToSharedLock() catch |err| return fail(diag, .{ .manifest_lock = err }); | |
| 1008 | } | |
| 1009 | ||
| 1010 | man.state = .hit; | |
| 1011 | return .hit; | |
| 1012 | } | |
| 1013 | ||
| 1014 | fn transitionToMissDiscovered(m: *Manifest) void { | |
| 1015 | switch (m.state) { | |
| 1016 | .input => unreachable, | |
| 1017 | .hit => unreachable, | |
| 1018 | .hit_digested => unreachable, | |
| 1019 | .miss => { | |
| 1020 | m.shrinkFilesToInput(); | |
| 1021 | m.state = .miss_discovered; | |
| 1022 | }, | |
| 1023 | .miss_discovered => {}, | |
| 1024 | .miss_digested => unreachable, | |
| 1025 | .miss_finalized => unreachable, | |
| 1026 | } | |
| 1027 | } | |
| 1028 | ||
| 1029 | fn shrinkFilesToInput(m: *Manifest) void { | |
| 1030 | if (m.files.count() <= m.input_paths.items.len) return; | |
| 1031 | // Reads from files hash map whose data is destroyed on the next line. | |
| 1032 | const off = m.files.keys()[m.input_paths.items.len]; | |
| 1033 | // Reads from the unshrunken contents whose data is destroyed on the next line. | |
| 1034 | m.files.shrinkRetainingCapacityContext(m.input_paths.items.len, .{ .contents = m.contents.items }); | |
| 1035 | m.contents.shrinkRetainingCapacity(@backingInt(off)); | |
| 1036 | assert(mem.isAligned(m.contents.items.len, @alignOf(File))); | |
| 1037 | } | |
| 1038 | ||
| 1039 | /// Does not observe or modify `self.hash.hasher`. Asserts `self.files` contains only the original input files. | |
| 1040 | fn checkLocked(m: *Manifest, diag: *CheckDiagnostic) CheckError!CheckResult { | |
| 1041 | assert(m.files.count() == m.input_paths.items.len); | |
| 1042 | const gpa = m.cache.gpa; | |
| 1043 | const io = m.cache.io; | |
| 1044 | const manifest_file = m.manifest_file.?; | |
| 1045 | ||
| 1046 | const manifest_stat = manifest_file.stat(io) catch |err| switch (err) { | |
| 1047 | error.Canceled => |e| return e, | |
| 1048 | else => |e| return fail(diag, .{ .manifest_stat = e }), | |
| 1049 | }; | |
| 1050 | const manifest_size = std.math.cast(u32, manifest_stat.size) orelse | |
| 1051 | return fail(diag, .manifest_oversize); | |
| 1052 | ||
| 1053 | if (manifest_size == 0 or manifest_size < m.contents.items.len) { | |
| 1054 | // Manifest file was never finalized. | |
| 1055 | try m.contents.ensureUnusedCapacity(gpa, 1); | |
| 1056 | return populateMissingInputFileHashes(m, diag, 0, .incomplete_manifest); | |
| 1057 | } | |
| 1058 | ||
| 1059 | // We must not clobber existing `Manifest.contents` because it possibly contains prepopulated stat and digest | |
| 1060 | // information which we must compare with the file system. Since non input files are typically greater in number | |
| 1061 | // than input files, the strategy here is to move the contents to make room for entirety of the manifest | |
| 1062 | // contents on disk, and then later truncate the contents array. | |
| 1063 | // | |
| 1064 | // The file on disk already supposedly includes the extra null byte that we have to maintain. | |
| 1065 | const input_contents_len = m.contents.items.len; | |
| 1066 | // After this resize, every return statement needs to modify m.contents length. | |
| 1067 | try m.contents.resize(gpa, input_contents_len + manifest_size); | |
| 1068 | errdefer m.contents.shrinkRetainingCapacity(input_contents_len); | |
| 1069 | @memcpy(m.contents.items[manifest_size..][0..input_contents_len], m.contents.items[0..input_contents_len]); | |
| 1070 | { | |
| 1071 | const n = manifest_file.readPositionalAll(io, m.contents.items, 0) catch |err| switch (err) { | |
| 1072 | error.Canceled => |e| return e, | |
| 1073 | else => |e| return fail(diag, .{ .manifest_read = e }), | |
| 1074 | }; | |
| 1075 | if (n != manifest_size) return missInput(m, diag, 0, input_contents_len, manifest_size, .incomplete_manifest); | |
| 1076 | } | |
| 1077 | const disk_contents = m.contents.items[0..manifest_size]; | |
| 1078 | const input_contents = m.contents.items[manifest_size..][0..input_contents_len]; | |
| 1079 | ||
| 1080 | // First the input files section, which must match our input files, otherwise it's invalid or incomplete. | |
| 1081 | var off: usize = 0; | |
| 1082 | for (m.input_paths.items, m.files.keys()[0..m.input_paths.items.len], 0..) |*input_path, file_off, i| { | |
| 1083 | if (@backingInt(file_off) + 1 >= disk_contents.len) | |
| 1084 | return missInput(m, diag, i, input_contents_len, manifest_size, .incomplete_manifest); | |
| 1085 | if (!mem.eql(u8, file_off.flagsAndPath(disk_contents), file_off.flagsAndPath(input_contents))) | |
| 1086 | return missInput(m, diag, i, input_contents_len, manifest_size, .invalid_manifest); | |
| 1087 | ||
| 1088 | const result = try checkInputPath(m, diag, file_off, input_path, disk_contents, input_contents); | |
| 1089 | if (result != .hit) return missInput(m, diag, i + 1, input_contents_len, manifest_size, result); | |
| 1090 | off = @backingInt(file_off); | |
| 1091 | } | |
| 1092 | ||
| 1093 | // Guess number of files based on manifest contents len to reduce allocations. | |
| 1094 | // This is not an upper bound; subsequent insertions may potentially allocate. | |
| 1095 | try m.files.ensureUnusedCapacityContext(gpa, disk_contents.len / (@sizeOf(File) + 32), .{ | |
| 1096 | .contents = disk_contents, | |
| 1097 | }); | |
| 1098 | ||
| 1099 | // Validate and check discovered files. | |
| 1100 | while (off + 1 < disk_contents.len) { | |
| 1101 | const file_off: File.Offset = @fromBackingInt(@intCast(off)); | |
| 1102 | const file = file_off.getFallible(disk_contents) catch return .invalid_manifest; | |
| 1103 | if (file.flags.prefix >= m.cache.prefixes_len) return .invalid_manifest; | |
| 1104 | const path = file_off.pathFallible(disk_contents) catch return .invalid_manifest; | |
| 1105 | if (path.len == 0) return .invalid_manifest; | |
| 1106 | ||
| 1107 | try m.files.putContext(gpa, file_off, {}, .{ .contents = disk_contents }); | |
| 1108 | const result = try checkDiscoveredPath(m, diag, file_off, disk_contents); | |
| 1109 | if (result != .hit) return result; | |
| 1110 | ||
| 1111 | off += File.sizeOf(path.len); | |
| 1112 | } | |
| 1113 | ||
| 1114 | // Final terminating zero byte to distinguish empty manifest file from | |
| 1115 | // manifest with zero files. | |
| 1116 | const file_valid = off + 1 == disk_contents.len and disk_contents[off] == 0; | |
| 1117 | if (!file_valid) return .incomplete_manifest; | |
| 1118 | ||
| 1119 | // Since it's a cache hit, we accept the input file contents from disk and discard the other copy. | |
| 1120 | // Furthermore, don't track the trailing zero byte in contents. | |
| 1121 | m.contents.shrinkRetainingCapacity(manifest_size - 1); | |
| 1122 | return .hit; | |
| 1123 | } | |
| 1124 | ||
| 1125 | /// Restore input file contents to prepare for cache miss workflow. We want to keep everything prior to `off` to | |
| 1126 | /// avoid redundant computation of digest and stat. | |
| 1127 | /// | |
| 1128 | /// Furthermore, this function is responsible for populating the remaining input file digests and metadata after the | |
| 1129 | /// missed one. | |
| 1130 | fn missInput( | |
| 1131 | m: *Manifest, | |
| 1132 | diag: *CheckDiagnostic, | |
| 1133 | next_file_index: usize, | |
| 1134 | input_contents_len: usize, | |
| 1135 | manifest_size: usize, | |
| 1136 | result: CheckResult, | |
| 1137 | ) CheckError!CheckResult { | |
| 1138 | const file_offs = m.files.keys(); | |
| 1139 | const off = if (file_offs.len - next_file_index == 0) | |
| 1140 | input_contents_len | |
| 1141 | else | |
| 1142 | @backingInt(file_offs[next_file_index]); | |
| 1143 | const copy_len = input_contents_len - off; | |
| 1144 | if (manifest_size > 0) @memcpy( | |
| 1145 | m.contents.items[off..][0..copy_len], | |
| 1146 | m.contents.items[manifest_size + off ..][0..copy_len], | |
| 1147 | ); | |
| 1148 | m.contents.shrinkRetainingCapacity(input_contents_len); | |
| 1149 | return populateMissingInputFileHashes(m, diag, next_file_index, result); | |
| 1150 | } | |
| 1151 | ||
| 1152 | fn populateMissingInputFileHashes( | |
| 1153 | m: *Manifest, | |
| 1154 | diag: *CheckDiagnostic, | |
| 1155 | next_file_index: usize, | |
| 1156 | result: CheckResult, | |
| 1157 | ) CheckError!CheckResult { | |
| 1158 | const file_offs = m.files.keys(); | |
| 1159 | const contents = m.contents.items; | |
| 1160 | for (file_offs[next_file_index..], m.input_paths.items[next_file_index..]) |input_file_off, *input_path| { | |
| 1161 | try populateInputPath(m, diag, input_file_off, input_path, contents); | |
| 1162 | } | |
| 1163 | return result; | |
| 1164 | } | |
| 1165 | ||
| 1166 | /// Similar to `checkInputPath` and `checkDiscoveredPath` but does not compare metadata or digest against file | |
| 1167 | /// system. Only ensures that metadata and digest are available. | |
| 1168 | fn populateInputPath( | |
| 1169 | m: *Manifest, | |
| 1170 | diag: *CheckDiagnostic, | |
| 1171 | file_off: File.Offset, | |
| 1172 | input_path: *InputPath, | |
| 1173 | contents: []u8, | |
| 1174 | ) CheckError!void { | |
| 1175 | const cache = m.cache; | |
| 1176 | const io = cache.io; | |
| 1177 | const input_file = file_off.get(contents); | |
| 1178 | const parent_dir = cache.prefixes()[input_file.flags.prefix].handle; | |
| 1179 | const file_path = file_off.path(contents); | |
| 1180 | const gpa = cache.gpa; | |
| 1181 | ||
| 1182 | if (input_path.have_digest) return; | |
| 1183 | ||
| 1184 | const stat_path_ok = switch (input_path.contents.unwrap()) { | |
| 1185 | .populated => true, | |
| 1186 | .unrequested, .requested => input_file.flags.metadata_only, | |
| 1187 | }; | |
| 1188 | if (!input_path.have_stat and stat_path_ok) { | |
| 1189 | // Since this is an input file, FileNotFound counts as a failure, not a cache miss. | |
| 1190 | const actual_stat = switch (input_path.getHandle(input_file)) { | |
| 1191 | .none => parent_dir.statFile(io, file_path, .{}) catch |err| switch (err) { | |
| 1192 | error.Canceled => |e| return e, | |
| 1193 | else => |e| return fail(diag, .{ .file_stat = .{ | |
| 1194 | .file_offset = file_off, | |
| 1195 | .err = e, | |
| 1196 | } }), | |
| 1197 | }, | |
| 1198 | .file => |opened_file| opened_file.stat(io) catch |err| switch (err) { | |
| 1199 | error.Canceled => |e| return e, | |
| 1200 | else => |e| return fail(diag, .{ .file_stat = .{ | |
| 1201 | .file_offset = file_off, | |
| 1202 | .err = e, | |
| 1203 | } }), | |
| 1204 | }, | |
| 1205 | .dir => |opened_dir| opened_dir.stat(io) catch |err| switch (err) { | |
| 1206 | error.Canceled => |e| return e, | |
| 1207 | else => |e| return fail(diag, .{ .file_stat = .{ | |
| 1208 | .file_offset = file_off, | |
| 1209 | .err = e, | |
| 1210 | } }), | |
| 1211 | }, | |
| 1212 | }; | |
| 1213 | const actual_is_directory = actual_stat.kind == .directory; | |
| 1214 | if (actual_is_directory != input_file.flags.is_directory) { | |
| 1215 | // Since this is an input file, this is a failure, not a cache miss. | |
| 1216 | return fail(diag, .{ .file_stat = .{ | |
| 1217 | .file_offset = file_off, | |
| 1218 | .err = if (actual_is_directory) error.IsDir else error.NotDir, | |
| 1219 | } }); | |
| 1220 | } | |
| 1221 | try input_file.setStat(m, .init(actual_stat)); | |
| 1222 | } | |
| 1223 | ||
| 1224 | if (input_file.flags.metadata_only) { | |
| 1225 | assert(stat_path_ok); | |
| 1226 | input_file.hashFromMetadata(); | |
| 1227 | return; | |
| 1228 | } | |
| 1229 | ||
| 1230 | if (input_path.contents.get(m.all_input_content.items)) |file_or_dir_contents| { | |
| 1231 | assert(stat_path_ok); | |
| 1232 | var hasher = hasher_init; | |
| 1233 | hasher.update(file_or_dir_contents); | |
| 1234 | hasher.final(&input_file.digest); | |
| 1235 | return; | |
| 1236 | } | |
| 1237 | ||
| 1238 | if (input_file.flags.is_directory) { | |
| 1239 | const opened_dir = if (input_path.have_handle) | |
| 1240 | input_path.handle.dir | |
| 1241 | else | |
| 1242 | parent_dir.openDir(io, file_path, .{ | |
| 1243 | .iterate = true, | |
| 1244 | .access_sub_paths = false, | |
| 1245 | }) catch |err| switch (err) { | |
| 1246 | error.Canceled => |e| return e, | |
| 1247 | else => |e| return fail(diag, .{ .file_open = .{ | |
| 1248 | .file_offset = file_off, | |
| 1249 | .err = e, | |
| 1250 | } }), | |
| 1251 | }; | |
| 1252 | defer if (!input_path.request_handle) opened_dir.close(io) else { | |
| 1253 | input_path.handle = .{ .dir = opened_dir }; | |
| 1254 | input_path.have_handle = true; | |
| 1255 | }; | |
| 1256 | ||
| 1257 | if (!input_path.have_stat) { | |
| 1258 | assert(!stat_path_ok); | |
| 1259 | const actual_stat = opened_dir.stat(io) catch |err| switch (err) { | |
| 1260 | error.Canceled => |e| return e, | |
| 1261 | else => |e| return fail(diag, .{ .file_stat = .{ | |
| 1262 | .file_offset = file_off, | |
| 1263 | .err = e, | |
| 1264 | } }), | |
| 1265 | }; | |
| 1266 | try input_file.setStat(m, .init(actual_stat)); | |
| 1267 | } | |
| 1268 | ||
| 1269 | const dir_contents_start = m.all_input_content.items.len; | |
| 1270 | hashDir(gpa, io, opened_dir, &input_file.digest, &m.all_input_content) catch |err| switch (err) { | |
| 1271 | error.Canceled, error.OutOfMemory => |e| return e, | |
| 1272 | else => |e| return fail(diag, .{ .file_read = .{ | |
| 1273 | .file_offset = file_off, | |
| 1274 | .err = e, | |
| 1275 | } }), | |
| 1276 | }; | |
| 1277 | defer switch (input_path.contents.unwrap()) { | |
| 1278 | .requested => input_path.contents = .populated(.{ | |
| 1279 | .off = @intCast(dir_contents_start), | |
| 1280 | .len = @intCast(m.all_input_content.items.len - dir_contents_start), | |
| 1281 | }), | |
| 1282 | .unrequested => m.all_input_content.shrinkRetainingCapacity(dir_contents_start), | |
| 1283 | .populated => unreachable, | |
| 1284 | }; | |
| 1285 | } else { | |
| 1286 | const opened_file = if (input_path.have_handle) | |
| 1287 | input_path.handle.file | |
| 1288 | else | |
| 1289 | parent_dir.openFile(io, file_path, .{ .mode = .read_only }) catch |err| switch (err) { | |
| 1290 | error.Canceled => |e| return e, | |
| 1291 | else => |e| return fail(diag, .{ .file_open = .{ | |
| 1292 | .file_offset = file_off, | |
| 1293 | .err = e, | |
| 1294 | } }), | |
| 1295 | }; | |
| 1296 | defer if (!input_path.request_handle) opened_file.close(io) else { | |
| 1297 | input_path.handle = .{ .file = opened_file }; | |
| 1298 | input_path.have_handle = true; | |
| 1299 | }; | |
| 1300 | ||
| 1301 | if (!input_path.have_stat) { | |
| 1302 | assert(!stat_path_ok); | |
| 1303 | const actual_stat = opened_file.stat(io) catch |err| switch (err) { | |
| 1304 | error.Canceled => |e| return e, | |
| 1305 | else => |e| return fail(diag, .{ .file_stat = .{ | |
| 1306 | .file_offset = file_off, | |
| 1307 | .err = e, | |
| 1308 | } }), | |
| 1309 | }; | |
| 1310 | try input_file.setStat(m, .init(actual_stat)); | |
| 1311 | } | |
| 1312 | ||
| 1313 | switch (input_path.contents.unwrap()) { | |
| 1314 | .requested => { | |
| 1315 | const start = m.all_input_content.items.len; | |
| 1316 | hashFileAppend(io, opened_file, &input_file.digest, &m.all_input_content, gpa) catch |err| switch (err) { | |
| 1317 | error.Canceled, error.OutOfMemory => |e| return e, | |
| 1318 | else => |e| return fail(diag, .{ .file_read = .{ | |
| 1319 | .file_offset = file_off, | |
| 1320 | .err = e, | |
| 1321 | } }), | |
| 1322 | }; | |
| 1323 | input_path.contents = .populated(.{ | |
| 1324 | .off = @intCast(start), | |
| 1325 | .len = @intCast(m.all_input_content.items.len - start), | |
| 1326 | }); | |
| 1327 | }, | |
| 1328 | .unrequested => hashFile(io, opened_file, &input_file.digest) catch |err| switch (err) { | |
| 1329 | error.Canceled => |e| return e, | |
| 1330 | else => |e| return fail(diag, .{ .file_read = .{ | |
| 1331 | .file_offset = file_off, | |
| 1332 | .err = e, | |
| 1333 | } }), | |
| 566 | 1334 | }, |
| 1335 | .populated => unreachable, | |
| 567 | 1336 | } |
| 568 | 1337 | } |
| 1338 | } | |
| 569 | 1339 | |
| 570 | self.want_refresh_timestamp = true; | |
| 571 | ||
| 572 | const input_file_count = self.files.entries.len; | |
| 573 | ||
| 574 | // We're going to construct a second hash. Its input will begin with the digest we've | |
| 575 | // already computed (`bin_digest`), and then it'll have the digests of each input file, | |
| 576 | // including "post" files (see `addFilePost`). If this is a hit, we learn the set of "post" | |
| 577 | // files from the manifest on disk. If this is a miss, we'll learn those from future calls | |
| 578 | // to `addFilePost` etc. As such, the state of `self.hash.hasher` after this function | |
| 579 | // depends on whether this is a hit or a miss. | |
| 580 | // | |
| 581 | // If we return `true` indicating a cache hit, then `self.hash.hasher` must already include | |
| 582 | // the digests of the "post" files, so the caller can call `final`. Otherwise, on a cache | |
| 583 | // miss, `self.hash.hasher` will include the digests of all non-"post" files -- that is, | |
| 584 | // the ones we've already been told about. The rest will be discovered through calls to | |
| 585 | // `addFilePost` etc, which will update the hasher. After all files are added, the user can | |
| 586 | // use `final`, and will at some point `writeManifest` the file list to disk. | |
| 1340 | /// Upon return, disk_contents digest and stat will be populated regardless of whether hit or miss occurs. | |
| 1341 | fn checkInputPath( | |
| 1342 | m: *Manifest, | |
| 1343 | diag: *CheckDiagnostic, | |
| 1344 | file_off: File.Offset, | |
| 1345 | input_path: *InputPath, | |
| 1346 | disk_contents: []u8, | |
| 1347 | input_contents: []u8, | |
| 1348 | ) CheckError!CheckResult { | |
| 1349 | const cache = m.cache; | |
| 1350 | const io = cache.io; | |
| 1351 | const gpa = cache.gpa; | |
| 1352 | const disk_file = file_off.get(disk_contents); | |
| 1353 | const input_file = file_off.get(input_contents); | |
| 1354 | const parent_dir = cache.prefixes()[disk_file.flags.prefix].handle; | |
| 1355 | const file_path = file_off.path(disk_contents); | |
| 1356 | ||
| 1357 | assert(disk_file.flags == input_file.flags); | |
| 1358 | ||
| 1359 | if (input_path.have_digest) { | |
| 1360 | if (mem.eql(u8, &disk_file.digest, &input_file.digest)) | |
| 1361 | return .hit; | |
| 1362 | } | |
| 587 | 1363 | |
| 588 | self.hash.hasher = hasher_init; | |
| 589 | self.hash.hasher.update(&bin_digest); | |
| 1364 | if (input_path.have_stat) { | |
| 1365 | const changed = try disk_file.setStatChanged(m, input_file.stat()); | |
| 1366 | if (!changed) return .hit; | |
| 1367 | } | |
| 590 | 1368 | |
| 591 | hit: { | |
| 592 | const file_digests_populated: usize = digests: { | |
| 593 | switch (try self.hitWithCurrentLock()) { | |
| 594 | .hit => break :hit, | |
| 595 | .miss => |m| if (!try self.upgradeToExclusiveLock()) { | |
| 596 | break :digests m.file_digests_populated; | |
| 1369 | if (disk_file.flags.metadata_only) { | |
| 1370 | if (!input_path.have_stat) { | |
| 1371 | // Since this is an input file, FileNotFound counts as a failure, not a cache miss. | |
| 1372 | const actual_stat = switch (input_path.getHandle(input_file)) { | |
| 1373 | .none => parent_dir.statFile(io, file_path, .{}) catch |err| switch (err) { | |
| 1374 | error.Canceled => |e| return e, | |
| 1375 | else => |e| return fail(diag, .{ .file_stat = .{ | |
| 1376 | .file_offset = file_off, | |
| 1377 | .err = e, | |
| 1378 | } }), | |
| 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. Someone | |
| 600 | // else might have modified the digest, so we need to check again before deciding to miss. | |
| 601 | // Before trying again, we must reset `self.hash.hasher` and `self.files`. | |
| 602 | // This is basically just the first half of `unhit`. | |
| 603 | self.hash.hasher = hasher_init; | |
| 604 | self.hash.hasher.update(&bin_digest); | |
| 605 | while (self.files.count() != input_file_count) { | |
| 606 | var file = self.files.pop().?; | |
| 607 | file.key.deinit(self.cache.gpa); | |
| 608 | } | |
| 609 | switch (try self.hitWithCurrentLock()) { | |
| 610 | .hit => break :hit, | |
| 611 | .miss => |m| break :digests m.file_digests_populated, | |
| 612 | } | |
| 613 | }; | |
| 614 | ||
| 615 | // This is a guaranteed cache miss. We're almost ready to return `false`, but there's a | |
| 616 | // little bookkeeping to do first. The first `file_digests_populated` entries in `files` | |
| 617 | // have their `bin_digest` populated; there may be some left in `input_file_count` which | |
| 618 | // we'll need to populate ourselves. Other than that, this is basically `unhit`. | |
| 619 | self.manifest_dirty = true; | |
| 620 | self.hash.hasher = hasher_init; | |
| 621 | self.hash.hasher.update(&bin_digest); | |
| 622 | while (self.files.count() != input_file_count) { | |
| 623 | var file = self.files.pop().?; | |
| 624 | file.key.deinit(self.cache.gpa); | |
| 625 | } | |
| 626 | for (self.files.keys(), 0..) |*file, idx| { | |
| 627 | if (idx < file_digests_populated) { | |
| 628 | // `bin_digest` is already populated by `hitWithCurrentLock`, so we can use it directly. | |
| 629 | self.hash.hasher.update(&file.bin_digest); | |
| 630 | } else { | |
| 631 | self.populateFileHash(file) catch |err| { | |
| 632 | self.diagnostic = .{ .file_hash = .{ | |
| 633 | .file_index = idx, | |
| 634 | .err = err, | |
| 635 | } }; | |
| 636 | return error.CacheCheckFailed; | |
| 637 | }; | |
| 638 | } | |
| 1405 | const stat: Stat = .init(actual_stat); | |
| 1406 | const changed = try disk_file.setStatChanged(m, stat); | |
| 1407 | disk_file.hashFromMetadata(); | |
| 1408 | if (!changed) return .hit; | |
| 639 | 1409 | } |
| 640 | return false; | |
| 1410 | return .{ .metadata_changed = file_off }; | |
| 641 | 1411 | } |
| 642 | 1412 | |
| 643 | if (self.want_shared_lock) { | |
| 644 | self.downgradeToSharedLock() catch |err| { | |
| 645 | self.diagnostic = .{ .manifest_lock = err }; | |
| 646 | return error.CacheCheckFailed; | |
| 647 | }; | |
| 648 | } | |
| 1413 | if (input_path.contents.get(m.all_input_content.items)) |file_or_dir_contents| { | |
| 1414 | var hasher = hasher_init; | |
| 1415 | hasher.update(file_or_dir_contents); | |
| 1416 | hasher.final(&disk_file.digest); | |
| 649 | 1417 | |
| 650 | return true; | |
| 651 | } | |
| 1418 | if (mem.eql(u8, &disk_file.digest, &input_file.digest)) return .hit; | |
| 1419 | return .{ .contents_changed = file_off }; | |
| 1420 | } | |
| 652 | 1421 | |
| 653 | /// Assumes that `self.hash.hasher` has been updated only with the original digest and that | |
| 654 | /// `self.files` contains only the original input files. | |
| 655 | fn hitWithCurrentLock(self: *Manifest) HitError!union(enum) { | |
| 656 | hit, | |
| 657 | miss: struct { | |
| 658 | file_digests_populated: usize, | |
| 659 | }, | |
| 660 | } { | |
| 661 | const gpa = self.cache.gpa; | |
| 662 | const io = self.cache.io; | |
| 663 | const input_file_count = self.files.entries.len; | |
| 664 | var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded | |
| 665 | var manifest_reader = self.manifest_file.?.reader(io, &tiny_buffer); // Reads positionally from zero. | |
| 666 | const limit: std.Io.Limit = .limited(manifest_file_size_max); | |
| 667 | const file_contents = manifest_reader.interface.allocRemaining(gpa, limit) catch |err| switch (err) { | |
| 668 | error.OutOfMemory => |e| return e, | |
| 669 | error.StreamTooLong => return error.OutOfMemory, | |
| 670 | error.ReadFailed => { | |
| 671 | self.diagnostic = .{ .manifest_read = manifest_reader.err.? }; | |
| 672 | return error.CacheCheckFailed; | |
| 673 | }, | |
| 674 | }; | |
| 675 | defer gpa.free(file_contents); | |
| 676 | ||
| 677 | var any_file_changed = false; | |
| 678 | var line_iter = mem.tokenizeScalar(u8, file_contents, '\n'); | |
| 679 | var idx: usize = 0; | |
| 680 | const header_valid = valid: { | |
| 681 | const line = line_iter.next() orelse break :valid false; | |
| 682 | break :valid std.mem.eql(u8, line, manifest_header); | |
| 683 | }; | |
| 684 | if (!header_valid) { | |
| 685 | return .{ .miss = .{ .file_digests_populated = 0 } }; | |
| 686 | } | |
| 687 | while (line_iter.next()) |line| { | |
| 688 | defer idx += 1; | |
| 689 | ||
| 690 | var iter = mem.tokenizeScalar(u8, line, ' '); | |
| 691 | const size = iter.next() orelse return error.InvalidFormat; | |
| 692 | const inode = iter.next() orelse return error.InvalidFormat; | |
| 693 | const mtime_nsec_str = iter.next() orelse return error.InvalidFormat; | |
| 694 | const digest_str = iter.next() orelse return error.InvalidFormat; | |
| 695 | const prefix_str = iter.next() orelse return error.InvalidFormat; | |
| 696 | const file_path = iter.rest(); | |
| 697 | ||
| 698 | const stat_size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat; | |
| 699 | const stat_inode = fmt.parseInt(Io.File.INode, inode, 10) catch return error.InvalidFormat; | |
| 700 | const stat_mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat; | |
| 701 | const file_bin_digest = b: { | |
| 702 | if (digest_str.len != hex_digest_len) return error.InvalidFormat; | |
| 703 | var bd: BinDigest = undefined; | |
| 704 | _ = fmt.hexToBytes(&bd, digest_str) catch return error.InvalidFormat; | |
| 705 | break :b bd; | |
| 1422 | if (disk_file.flags.is_directory) { | |
| 1423 | const opened_dir = if (input_path.have_handle) | |
| 1424 | input_path.handle.dir | |
| 1425 | else | |
| 1426 | parent_dir.openDir(io, file_path, .{ | |
| 1427 | .iterate = true, | |
| 1428 | .access_sub_paths = false, | |
| 1429 | }) catch |err| switch (err) { | |
| 1430 | error.Canceled => |e| return e, | |
| 1431 | else => |e| return fail(diag, .{ .file_open = .{ | |
| 1432 | .file_offset = file_off, | |
| 1433 | .err = e, | |
| 1434 | } }), | |
| 1435 | }; | |
| 1436 | defer if (!input_path.request_handle) opened_dir.close(io) else { | |
| 1437 | input_path.handle = .{ .dir = opened_dir }; | |
| 1438 | input_path.have_handle = true; | |
| 706 | 1439 | }; |
| 707 | 1440 | |
| 708 | const prefix = fmt.parseInt(u8, prefix_str, 10) catch return error.InvalidFormat; | |
| 709 | if (prefix >= self.cache.prefixes_len) return error.InvalidFormat; | |
| 1441 | if (!input_path.have_stat) { | |
| 1442 | const actual_stat = opened_dir.stat(io) catch |err| switch (err) { | |
| 1443 | error.Canceled => |e| return e, | |
| 1444 | else => |e| return fail(diag, .{ .file_stat = .{ | |
| 1445 | .file_offset = file_off, | |
| 1446 | .err = e, | |
| 1447 | } }), | |
| 1448 | }; | |
| 1449 | if (!try disk_file.setStatChanged(m, .init(actual_stat))) return .hit; | |
| 1450 | } | |
| 710 | 1451 | |
| 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 | }; | |
| 712 | 1468 | |
| 713 | const cache_hash_file = f: { | |
| 714 | const prefixed_path: PrefixedPath = .{ | |
| 715 | .prefix = prefix, | |
| 716 | .sub_path = file_path, // expires with file_contents | |
| 1469 | if (mem.eql(u8, &disk_file.digest, &input_file.digest)) return .hit; | |
| 1470 | return .{ .contents_changed = file_off }; | |
| 1471 | } else { | |
| 1472 | const opened_file = if (input_path.have_handle) | |
| 1473 | input_path.handle.file | |
| 1474 | else | |
| 1475 | parent_dir.openFile(io, file_path, .{ .mode = .read_only }) catch |err| switch (err) { | |
| 1476 | error.Canceled => |e| return e, | |
| 1477 | else => |e| return fail(diag, .{ .file_open = .{ | |
| 1478 | .file_offset = file_off, | |
| 1479 | .err = e, | |
| 1480 | } }), | |
| 717 | 1481 | }; |
| 718 | if (idx < input_file_count) { | |
| 719 | const file = &self.files.keys()[idx]; | |
| 720 | if (!file.prefixed_path.eql(prefixed_path)) | |
| 721 | return error.InvalidFormat; | |
| 722 | ||
| 723 | file.stat = .{ | |
| 724 | .size = stat_size, | |
| 725 | .inode = stat_inode, | |
| 726 | .mtime = .{ .nanoseconds = stat_mtime }, | |
| 727 | }; | |
| 728 | file.bin_digest = file_bin_digest; | |
| 729 | break :f file; | |
| 730 | } | |
| 731 | const gop = try self.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{}); | |
| 732 | errdefer _ = self.files.pop(); | |
| 733 | if (!gop.found_existing) { | |
| 734 | gop.key_ptr.* = .{ | |
| 735 | .prefixed_path = .{ | |
| 736 | .prefix = prefix, | |
| 737 | .sub_path = try gpa.dupe(u8, file_path), | |
| 738 | }, | |
| 739 | .contents = null, | |
| 740 | .max_file_size = null, | |
| 741 | .handle = null, | |
| 742 | .stat = .{ | |
| 743 | .size = stat_size, | |
| 744 | .inode = stat_inode, | |
| 745 | .mtime = .{ .nanoseconds = stat_mtime }, | |
| 746 | }, | |
| 747 | .bin_digest = file_bin_digest, | |
| 748 | }; | |
| 749 | } | |
| 750 | break :f gop.key_ptr; | |
| 1482 | defer if (!input_path.request_handle) opened_file.close(io) else { | |
| 1483 | input_path.handle = .{ .file = opened_file }; | |
| 1484 | input_path.have_handle = true; | |
| 751 | 1485 | }; |
| 752 | 1486 | |
| 753 | const pp = cache_hash_file.prefixed_path; | |
| 754 | const dir = self.cache.prefixes()[pp.prefix].handle; | |
| 755 | const this_file = dir.openFile(io, pp.sub_path, .{ .mode = .read_only }) catch |err| switch (err) { | |
| 756 | error.FileNotFound => { | |
| 757 | // Every digest before this one has been populated successfully. | |
| 758 | return .{ .miss = .{ .file_digests_populated = idx } }; | |
| 1487 | if (!input_path.have_stat) { | |
| 1488 | const actual_stat = opened_file.stat(io) catch |err| switch (err) { | |
| 1489 | error.Canceled => |e| return e, | |
| 1490 | else => |e| return fail(diag, .{ .file_stat = .{ | |
| 1491 | .file_offset = file_off, | |
| 1492 | .err = e, | |
| 1493 | } }), | |
| 1494 | }; | |
| 1495 | if (!try disk_file.setStatChanged(m, .init(actual_stat))) return .hit; | |
| 1496 | } | |
| 1497 | ||
| 1498 | switch (input_path.contents.unwrap()) { | |
| 1499 | .requested => { | |
| 1500 | const start = m.all_input_content.items.len; | |
| 1501 | hashFileAppend(io, opened_file, &disk_file.digest, &m.all_input_content, gpa) catch |err| switch (err) { | |
| 1502 | error.Canceled, error.OutOfMemory => |e| return e, | |
| 1503 | else => |e| return fail(diag, .{ .file_read = .{ | |
| 1504 | .file_offset = file_off, | |
| 1505 | .err = e, | |
| 1506 | } }), | |
| 1507 | }; | |
| 1508 | input_path.contents = .populated(.{ | |
| 1509 | .off = @intCast(start), | |
| 1510 | .len = @intCast(m.all_input_content.items.len - start), | |
| 1511 | }); | |
| 759 | 1512 | }, |
| 760 | error.Canceled => |e| return e, | |
| 761 | else => |e| { | |
| 762 | self.diagnostic = .{ .file_open = .{ | |
| 763 | .file_index = idx, | |
| 1513 | .unrequested => hashFile(io, opened_file, &disk_file.digest) catch |err| switch (err) { | |
| 1514 | error.Canceled => |e| return e, | |
| 1515 | else => |e| return fail(diag, .{ .file_read = .{ | |
| 1516 | .file_offset = file_off, | |
| 764 | 1517 | .err = e, |
| 765 | } }; | |
| 766 | return error.CacheCheckFailed; | |
| 1518 | } }), | |
| 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 | }; | |
| 788 | 1550 | |
| 789 | if (try self.isProblematicTimestamp(cache_hash_file.stat.mtime)) { | |
| 790 | // The actual file has an unreliable timestamp, force it to be hashed | |
| 791 | cache_hash_file.stat.mtime = .zero; | |
| 792 | cache_hash_file.stat.inode = 0; | |
| 793 | } | |
| 1551 | const actual_is_directory = actual_stat.kind == .directory; | |
| 1552 | if (actual_is_directory != file.flags.is_directory) return .{ .directory_status_changed = file_off }; | |
| 794 | 1553 | |
| 795 | var actual_digest: BinDigest = undefined; | |
| 796 | hashFile(io, this_file, &actual_digest) catch |err| { | |
| 797 | self.diagnostic = .{ .file_read = .{ | |
| 798 | .file_index = idx, | |
| 799 | .err = err, | |
| 800 | } }; | |
| 801 | return error.CacheCheckFailed; | |
| 802 | }; | |
| 1554 | const changed = try file.setStatChanged(m, .init(actual_stat)); | |
| 1555 | file.hashFromMetadata(); | |
| 1556 | if (changed) return .{ .metadata_changed = file_off }; | |
| 1557 | return .hit; | |
| 1558 | } | |
| 803 | 1559 | |
| 804 | if (!mem.eql(u8, &cache_hash_file.bin_digest, &actual_digest)) { | |
| 805 | cache_hash_file.bin_digest = actual_digest; | |
| 806 | // keep going until we have the input file digests | |
| 807 | any_file_changed = true; | |
| 808 | } | |
| 809 | } | |
| 1560 | if (file.flags.is_directory) { | |
| 1561 | const opened_dir = parent_dir.openDir(io, file_path, .{ | |
| 1562 | .iterate = true, | |
| 1563 | .access_sub_paths = false, | |
| 1564 | }) catch |err| switch (err) { | |
| 1565 | error.FileNotFound => return .{ .path_deleted = file_off }, | |
| 1566 | error.NotDir => return .{ .directory_status_changed = file_off }, | |
| 1567 | error.Canceled => |e| return e, | |
| 1568 | else => |e| return fail(diag, .{ .file_open = .{ | |
| 1569 | .file_offset = file_off, | |
| 1570 | .err = e, | |
| 1571 | } }), | |
| 1572 | }; | |
| 1573 | defer opened_dir.close(io); | |
| 1574 | ||
| 1575 | const actual_stat = opened_dir.stat(io) catch |err| switch (err) { | |
| 1576 | error.Canceled => |e| return e, | |
| 1577 | else => |e| return fail(diag, .{ .file_stat = .{ | |
| 1578 | .file_offset = file_off, | |
| 1579 | .err = e, | |
| 1580 | } }), | |
| 1581 | }; | |
| 1582 | if (try file.setStatChanged(m, .init(actual_stat))) { | |
| 1583 | const prev_digest: BinDigest = file.digest; | |
| 1584 | const dir_contents_start = m.all_input_content.items.len; | |
| 1585 | defer m.all_input_content.shrinkRetainingCapacity(dir_contents_start); | |
| 1586 | hashDir(gpa, io, opened_dir, &file.digest, &m.all_input_content) catch |err| switch (err) { | |
| 1587 | error.Canceled, error.OutOfMemory => |e| return e, | |
| 1588 | else => |e| return fail(diag, .{ .file_read = .{ | |
| 1589 | .file_offset = file_off, | |
| 1590 | .err = e, | |
| 1591 | } }), | |
| 1592 | }; | |
| 810 | 1593 | |
| 811 | if (!any_file_changed) { | |
| 812 | self.hash.hasher.update(&cache_hash_file.bin_digest); | |
| 1594 | if (!mem.eql(u8, &file.digest, &prev_digest)) return .{ .contents_changed = file_off }; | |
| 813 | 1595 | } |
| 1596 | return .hit; | |
| 814 | 1597 | } |
| 815 | 1598 | |
| 816 | // If the manifest was somehow missing one of our input files, or if any file hash has changed, | |
| 817 | // then this is a cache miss. However, we have successfully populated some or all of the file | |
| 818 | // digests. | |
| 819 | if (any_file_changed or idx < input_file_count) { | |
| 820 | return .{ .miss = .{ .file_digests_populated = idx } }; | |
| 1599 | const opened_file = parent_dir.openFile(io, file_path, .{ .mode = .read_only }) catch |err| switch (err) { | |
| 1600 | error.FileNotFound => return .{ .path_deleted = file_off }, | |
| 1601 | error.IsDir => return .{ .directory_status_changed = file_off }, | |
| 1602 | error.Canceled => |e| return e, | |
| 1603 | else => |e| return fail(diag, .{ .file_open = .{ | |
| 1604 | .file_offset = file_off, | |
| 1605 | .err = e, | |
| 1606 | } }), | |
| 1607 | }; | |
| 1608 | defer opened_file.close(io); | |
| 1609 | ||
| 1610 | const actual_stat = opened_file.stat(io) catch |err| switch (err) { | |
| 1611 | error.Canceled => |e| return e, | |
| 1612 | else => |e| return fail(diag, .{ .file_stat = .{ | |
| 1613 | .file_offset = file_off, | |
| 1614 | .err = e, | |
| 1615 | } }), | |
| 1616 | }; | |
| 1617 | ||
| 1618 | if (try file.setStatChanged(m, .init(actual_stat))) { | |
| 1619 | const prev_digest: BinDigest = file.digest; | |
| 1620 | hashFile(io, opened_file, &file.digest) catch |err| switch (err) { | |
| 1621 | error.Canceled => |e| return e, | |
| 1622 | else => |e| return fail(diag, .{ .file_read = .{ | |
| 1623 | .file_offset = file_off, | |
| 1624 | .err = e, | |
| 1625 | } }), | |
| 1626 | }; | |
| 1627 | ||
| 1628 | if (!mem.eql(u8, &file.digest, &prev_digest)) return .{ .contents_changed = file_off }; | |
| 821 | 1629 | } |
| 822 | 1630 | |
| 823 | 1631 | return .hit; |
| 824 | 1632 | } |
| 825 | 1633 | |
| 826 | /// Reset `self.hash.hasher` to the state it should be in after `hit` returns `false`. | |
| 827 | /// The hasher contains the original input digest, and all original input file digests (i.e. | |
| 828 | /// not including post files). | |
| 829 | /// Assumes that `bin_digest` is populated for all files up to `input_file_count`. As such, | |
| 830 | /// this is not necessarily safe to call within `hit`. | |
| 831 | pub fn unhit(self: *Manifest, bin_digest: BinDigest, input_file_count: usize) void { | |
| 832 | // Reset the hash. | |
| 833 | self.hash.hasher = hasher_init; | |
| 834 | self.hash.hasher.update(&bin_digest); | |
| 835 | ||
| 836 | // Remove files not in the initial hash. | |
| 837 | while (self.files.count() != input_file_count) { | |
| 838 | var file = self.files.pop().?; | |
| 839 | file.key.deinit(self.cache.gpa); | |
| 840 | } | |
| 841 | ||
| 842 | for (self.files.keys()) |file| { | |
| 843 | self.hash.hasher.update(&file.bin_digest); | |
| 844 | } | |
| 1634 | fn fail(ptr: *CheckDiagnostic, d: CheckDiagnostic) error{CacheCheckFailed} { | |
| 1635 | ptr.* = d; | |
| 1636 | return error.CacheCheckFailed; | |
| 845 | 1637 | } |
| 846 | 1638 | |
| 847 | 1639 | fn isProblematicTimestamp(man: *Manifest, timestamp: Io.Timestamp) error{Canceled}!bool { |
| ... | ... | @@ -887,303 +1679,354 @@ pub const Manifest = struct { |
| 887 | 1679 | return timestamp.nanoseconds >= man.recent_problematic_timestamp.nanoseconds; |
| 888 | 1680 | } |
| 889 | 1681 | |
| 890 | fn populateFileHash(self: *Manifest, ch_file: *File) !void { | |
| 891 | const io = self.cache.io; | |
| 892 | ||
| 893 | if (ch_file.handle) |handle| { | |
| 894 | return populateFileHashHandle(self, ch_file, handle); | |
| 895 | } else { | |
| 896 | const pp = ch_file.prefixed_path; | |
| 897 | const dir = self.cache.prefixes()[pp.prefix].handle; | |
| 898 | const handle = try dir.openFile(io, pp.sub_path, .{}); | |
| 899 | defer handle.close(io); | |
| 900 | return populateFileHashHandle(self, ch_file, handle); | |
| 901 | } | |
| 902 | } | |
| 903 | ||
| 904 | fn populateFileHashHandle(self: *Manifest, ch_file: *File, io_file: Io.File) !void { | |
| 905 | const io = self.cache.io; | |
| 906 | const gpa = self.cache.gpa; | |
| 1682 | pub const AddDiscoveredPathOptions = struct { | |
| 1683 | discovered_path: DiscoveredPath, | |
| 1684 | handle: PathHandle = .{ .file = null }, | |
| 1685 | stat: ?Stat = null, | |
| 1686 | /// If it is a directory, there is a special encoding required for contents, which | |
| 1687 | /// is null-separated sorted entries, each one prefixed with `File.Kind`. | |
| 1688 | contents: ?[]const u8 = null, | |
| 1689 | metadata_only: bool = false, | |
| 1690 | /// Populated if and only if `error.FileSystemFailure` is returned from `addDiscoveredPath`. | |
| 1691 | diagnostic: ?*AddDiscoveredPathDiagnostic = null, | |
| 1692 | }; | |
| 907 | 1693 | |
| 908 | const actual_stat = try io_file.stat(io); | |
| 909 | ch_file.stat = .{ | |
| 910 | .size = actual_stat.size, | |
| 911 | .mtime = actual_stat.mtime, | |
| 912 | .inode = actual_stat.inode, | |
| 913 | }; | |
| 1694 | pub const DiscoveredPath = union(enum) { | |
| 1695 | unresolved: Path, | |
| 1696 | prefixed: PrefixedPath, | |
| 1697 | }; | |
| 914 | 1698 | |
| 915 | if (try self.isProblematicTimestamp(ch_file.stat.mtime)) { | |
| 916 | // The actual file has an unreliable timestamp, force it to be hashed | |
| 917 | ch_file.stat.mtime = .zero; | |
| 918 | ch_file.stat.inode = 0; | |
| 1699 | pub const AddDiscoveredPathDiagnostic = union(enum) { | |
| 1700 | none, | |
| 1701 | open_file: Io.File.OpenError, | |
| 1702 | open_dir: Io.Dir.OpenError, | |
| 1703 | stat_file: Io.File.StatError, | |
| 1704 | stat_dir: Io.Dir.StatError, | |
| 1705 | read_file: Io.File.ReadPositionalError, | |
| 1706 | read_dir: Io.Dir.Reader.Error, | |
| 1707 | ||
| 1708 | pub fn format(this: @This(), w: *Io.Writer) Io.Writer.Error!void { | |
| 1709 | switch (this) { | |
| 1710 | .none => return w.writeAll("none"), | |
| 1711 | else => |err, tag| return w.print("{t}: {t}", .{ tag, err }), | |
| 1712 | } | |
| 919 | 1713 | } |
| 1714 | }; | |
| 920 | 1715 | |
| 921 | if (ch_file.max_file_size) |max_file_size| { | |
| 922 | if (ch_file.stat.size > max_file_size) return error.FileTooBig; | |
| 1716 | pub const AddDiscoveredPathError = error{ | |
| 1717 | /// If this is returned, diagnostic will be populated. | |
| 1718 | /// If contents and stat are both provided, this is unreachable. | |
| 1719 | FileSystemFailure, | |
| 1720 | } || Allocator.Error || Io.Cancelable; | |
| 923 | 1721 | |
| 924 | // Hash while reading from disk, to keep the contents in the cpu | |
| 925 | // cache while doing hashing. | |
| 926 | const contents = try gpa.alloc(u8, @intCast(ch_file.stat.size)); | |
| 927 | errdefer gpa.free(contents); | |
| 1722 | /// Add a file or directory as a dependency of process being cached, after cache miss occurs. | |
| 1723 | /// | |
| 1724 | /// See also: | |
| 1725 | /// * `addInputPath` | |
| 1726 | /// * `addDiscoveredDepFile` | |
| 1727 | /// * `addDiscoveredManifest` | |
| 1728 | pub fn addDiscoveredPath(m: *Manifest, options: AddDiscoveredPathOptions) AddDiscoveredPathError!void { | |
| 1729 | assert(m.manifest_file != null); | |
| 1730 | transitionToMissDiscovered(m); | |
| 1731 | const cache = m.cache; | |
| 1732 | const gpa = cache.gpa; | |
| 1733 | const io = cache.io; | |
| 1734 | const is_directory = options.handle == .dir; | |
| 1735 | ||
| 1736 | try m.files.ensureUnusedCapacityContext(gpa, 1, .{ .contents = m.contents.items }); | |
| 1737 | ||
| 1738 | const new_file_offset: File.Offset = @fromBackingInt(@intCast(m.contents.items.len)); | |
| 1739 | try m.contents.appendNTimes(gpa, 0, @offsetOf(File, "path_start")); | |
| 1740 | errdefer m.contents.shrinkRetainingCapacity(@backingInt(new_file_offset)); | |
| 1741 | ||
| 1742 | const new_prefix = switch (options.discovered_path) { | |
| 1743 | .unresolved => |unresolved| try cache.resolveAppendPath(&m.contents, unresolved), | |
| 1744 | .prefixed => |prefixed| try cache.appendPrefixedPath(&m.contents, prefixed), | |
| 1745 | }; | |
| 1746 | assert(mem.isAligned(m.contents.items.len, @alignOf(File))); | |
| 1747 | new_file_offset.get(m.contents.items).flags = .{ | |
| 1748 | .prefix = new_prefix, | |
| 1749 | .is_directory = is_directory, | |
| 1750 | .metadata_only = options.metadata_only, | |
| 1751 | }; | |
| 928 | 1752 | |
| 929 | var hasher = hasher_init; | |
| 930 | var off: usize = 0; | |
| 931 | while (true) { | |
| 932 | const bytes_read = try io_file.readPositional(io, &.{contents[off..]}, off); | |
| 933 | if (bytes_read == 0) break; | |
| 934 | hasher.update(contents[off..][0..bytes_read]); | |
| 935 | off += bytes_read; | |
| 1753 | const gop = m.files.getOrPutAssumeCapacityContext(new_file_offset, .{ .contents = m.contents.items }); | |
| 1754 | m.files.lockPointers(); | |
| 1755 | defer m.files.unlockPointers(); | |
| 1756 | ||
| 1757 | const file_offset = if (gop.found_existing) h: { | |
| 1758 | m.contents.shrinkRetainingCapacity(@backingInt(new_file_offset)); | |
| 1759 | const existing_off = gop.key_ptr.*; | |
| 1760 | const header = existing_off.get(m.contents.items); | |
| 1761 | // If it trips, the same file path has been added to the cache | |
| 1762 | // manifest both as a directory and as a normal file, making the | |
| 1763 | // intended caching behavior ambiguous. | |
| 1764 | assert(header.flags.is_directory == is_directory); | |
| 1765 | if (!options.metadata_only) | |
| 1766 | header.flags.metadata_only = false; | |
| 1767 | break :h existing_off; | |
| 1768 | } else new_file_offset; | |
| 1769 | ||
| 1770 | const header = file_offset.get(m.contents.items); | |
| 1771 | ||
| 1772 | if (options.stat) |stat| { | |
| 1773 | try header.setStat(m, stat); | |
| 1774 | if (header.flags.metadata_only) { | |
| 1775 | header.hashFromMetadata(); | |
| 1776 | } else if (options.contents) |contents| { | |
| 1777 | var hasher = hasher_init; | |
| 1778 | hasher.update(contents); | |
| 1779 | hasher.final(&header.digest); | |
| 936 | 1780 | } |
| 937 | hasher.final(&ch_file.bin_digest); | |
| 938 | ||
| 939 | ch_file.contents = contents; | |
| 940 | } else { | |
| 941 | try hashFile(io, io_file, &ch_file.bin_digest); | |
| 1781 | return; | |
| 942 | 1782 | } |
| 943 | 1783 | |
| 944 | self.hash.hasher.update(&ch_file.bin_digest); | |
| 945 | } | |
| 946 | ||
| 947 | /// Add a file as a dependency of process being cached, after the initial hash has been | |
| 948 | /// calculated. This is useful for processes that don't know all the files that | |
| 949 | /// are depended on ahead of time. For example, a source file that can import other files | |
| 950 | /// will need to be recompiled if the imported file is changed. | |
| 951 | pub fn addFilePostFetch(self: *Manifest, file_path: []const u8, max_file_size: usize) ![]const u8 { | |
| 952 | assert(self.manifest_file != null); | |
| 953 | ||
| 954 | const gpa = self.cache.gpa; | |
| 955 | const prefixed_path = try self.cache.findPrefix(file_path); | |
| 956 | errdefer gpa.free(prefixed_path.sub_path); | |
| 957 | ||
| 958 | const gop = try self.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{}); | |
| 959 | errdefer _ = self.files.pop(); | |
| 1784 | const need_stat = options.stat == null; | |
| 1785 | const metadata_only = header.flags.metadata_only; | |
| 1786 | const prefix = header.flags.prefix; | |
| 1787 | ||
| 1788 | switch (options.handle) { | |
| 1789 | .dir => |opt_handle| if (opt_handle) |handle| { | |
| 1790 | try populateDirectory(m, header, need_stat, handle, options.contents, metadata_only, options.diagnostic); | |
| 1791 | } else { | |
| 1792 | const dir = cache.prefixes()[prefix].handle; | |
| 1793 | const sub_path = file_offset.path(m.contents.items); | |
| 1794 | const handle = dir.openDir(io, sub_path, .{ | |
| 1795 | .access_sub_paths = false, | |
| 1796 | .iterate = true, | |
| 1797 | }) catch |err| switch (err) { | |
| 1798 | error.Canceled => |e| return e, | |
| 1799 | else => |e| { | |
| 1800 | if (options.diagnostic) |d| d.* = .{ .open_dir = e }; | |
| 1801 | return error.FileSystemFailure; | |
| 1802 | }, | |
| 1803 | }; | |
| 1804 | defer handle.close(io); | |
| 1805 | try populateDirectory(m, header, need_stat, handle, options.contents, metadata_only, options.diagnostic); | |
| 1806 | }, | |
| 960 | 1807 | |
| 961 | if (gop.found_existing) { | |
| 962 | gpa.free(prefixed_path.sub_path); | |
| 963 | return gop.key_ptr.contents.?; | |
| 1808 | .file => |opt_handle| if (opt_handle) |handle| { | |
| 1809 | try populateFile(m, header, need_stat, handle, options.contents, metadata_only, options.diagnostic); | |
| 1810 | } else { | |
| 1811 | const dir = cache.prefixes()[prefix].handle; | |
| 1812 | const sub_path = file_offset.path(m.contents.items); | |
| 1813 | const handle = dir.openFile(io, sub_path, .{ .mode = .read_only }) catch |err| switch (err) { | |
| 1814 | error.Canceled => |e| return e, | |
| 1815 | else => |e| { | |
| 1816 | if (options.diagnostic) |d| d.* = .{ .open_file = e }; | |
| 1817 | return error.FileSystemFailure; | |
| 1818 | }, | |
| 1819 | }; | |
| 1820 | defer handle.close(io); | |
| 1821 | try populateFile(m, header, need_stat, handle, options.contents, metadata_only, options.diagnostic); | |
| 1822 | }, | |
| 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 | } |
| 981 | 1825 | |
| 982 | /// Add a file as a dependency of process being cached, after the initial hash has been | |
| 983 | /// calculated. | |
| 984 | /// | |
| 985 | /// This is useful for processes that don't know the all the files that are | |
| 986 | /// depended on ahead of time. For example, a source file that can import | |
| 987 | /// other files will need to be recompiled if the imported file is changed. | |
| 988 | pub fn addFilePost(man: *Manifest, file_path: []const u8) !void { | |
| 989 | assert(man.manifest_file != null); | |
| 990 | const gpa = man.cache.gpa; | |
| 991 | const prefixed_path = try man.cache.findPrefix(file_path); | |
| 992 | var keep = false; | |
| 993 | defer if (!keep) gpa.free(prefixed_path.sub_path); | |
| 994 | keep = try addPrefixedPathPost(man, prefixed_path); | |
| 995 | } | |
| 996 | ||
| 997 | pub fn addPathPost(man: *Manifest, path: Path) !void { | |
| 998 | assert(man.manifest_file != null); | |
| 999 | const gpa = man.cache.gpa; | |
| 1000 | const prefixed_path: PrefixedPath = try man.cache.findPrefixPath(path); | |
| 1001 | var keep = false; | |
| 1002 | defer if (!keep) gpa.free(prefixed_path.sub_path); | |
| 1003 | keep = try addPrefixedPathPost(man, prefixed_path); | |
| 1004 | } | |
| 1005 | ||
| 1006 | /// Low level function. `prefixed_path` references cloned memory. Returns | |
| 1007 | /// whether or not `prefixed_path.sub_path` should be kept. | |
| 1008 | pub fn addPrefixedPathPost(man: *Manifest, prefixed_path: PrefixedPath) !bool { | |
| 1009 | assert(man.manifest_file != null); | |
| 1010 | const gpa = man.cache.gpa; | |
| 1826 | pub const AddDiscoveredDepFileError = error{ | |
| 1827 | InvalidDepFile, | |
| 1828 | } || AddDiscoveredPathError || Io.File.OpenError || Io.File.Reader.Error; | |
| 1011 | 1829 | |
| 1012 | const gop = try man.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{}); | |
| 1013 | errdefer _ = man.files.pop(); | |
| 1014 | ||
| 1015 | if (gop.found_existing) return false; | |
| 1830 | pub const AddDiscoveredDepFileDiagnostic = union(enum) { | |
| 1831 | add_discovered_path: AddDiscoveredPathDiagnostic, | |
| 1832 | dep_tokenizer: DepTokenizer.Token, | |
| 1833 | }; | |
| 1016 | 1834 | |
| 1017 | gop.key_ptr.* = .{ | |
| 1018 | .prefixed_path = prefixed_path, | |
| 1019 | .max_file_size = null, | |
| 1020 | .handle = null, | |
| 1021 | .stat = undefined, | |
| 1022 | .bin_digest = undefined, | |
| 1023 | .contents = null, | |
| 1835 | /// Add a GNU make style dep file as a dependency of process being cached, after cache miss occurs. | |
| 1836 | /// | |
| 1837 | /// See also: | |
| 1838 | /// * `addDiscoveredPath | |
| 1839 | /// * `addDiscoveredManifest` | |
| 1840 | pub fn addDiscoveredDepFile( | |
| 1841 | m: *Manifest, | |
| 1842 | path: Path, | |
| 1843 | diagnostic: ?*AddDiscoveredDepFileDiagnostic, | |
| 1844 | ) AddDiscoveredDepFileError!void { | |
| 1845 | assert(m.manifest_file != null); | |
| 1846 | transitionToMissDiscovered(m); | |
| 1847 | ||
| 1848 | const cache = m.cache; | |
| 1849 | const gpa = cache.gpa; | |
| 1850 | const io = cache.io; | |
| 1851 | ||
| 1852 | // TODO: change DepTokenizer to be streaming rather than operating on slice of bytes. | |
| 1853 | const prev_len = m.all_input_content.items.len; | |
| 1854 | defer m.all_input_content.items.len = prev_len; | |
| 1855 | ||
| 1856 | const file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); | |
| 1857 | defer file.close(io); | |
| 1858 | ||
| 1859 | var file_reader: Io.File.Reader = .init(file, io, &.{}); | |
| 1860 | file_reader.interface.appendRemainingUnlimited(gpa, &m.all_input_content) catch |err| switch (err) { | |
| 1861 | error.OutOfMemory => |e| return e, | |
| 1862 | error.ReadFailed => return file_reader.err.?, | |
| 1024 | 1863 | }; |
| 1025 | 1864 | |
| 1026 | man.files.lockPointers(); | |
| 1027 | defer man.files.unlockPointers(); | |
| 1028 | ||
| 1029 | try man.populateFileHash(gop.key_ptr); | |
| 1030 | return true; | |
| 1865 | var aux: std.ArrayList(u8) = .empty; | |
| 1866 | defer aux.deinit(gpa); | |
| 1867 | ||
| 1868 | var it: DepTokenizer = .{ .bytes = m.all_input_content.items[prev_len..] }; | |
| 1869 | while (it.next()) |token| switch (token) { | |
| 1870 | .target, .target_must_resolve => {}, | |
| 1871 | .prereq => |p| try m.addDiscoveredPath(.{ .discovered_path = .{ .unresolved = .initCwd(p) } }), | |
| 1872 | .prereq_must_resolve => { | |
| 1873 | aux.clearRetainingCapacity(); | |
| 1874 | try token.resolve(gpa, &aux); | |
| 1875 | try m.addDiscoveredPath(.{ .discovered_path = .{ .unresolved = .initCwd(aux.items) } }); | |
| 1876 | }, | |
| 1877 | else => |err| { | |
| 1878 | if (diagnostic) |d| d.* = .{ .dep_tokenizer = err }; | |
| 1879 | return error.InvalidDepFile; | |
| 1880 | }, | |
| 1881 | }; | |
| 1031 | 1882 | } |
| 1032 | 1883 | |
| 1033 | /// Like `addFilePost` but when the file contents have already been loaded from disk. | |
| 1034 | pub fn addFilePostContents( | |
| 1035 | man: *Manifest, | |
| 1036 | file_path: []const u8, | |
| 1037 | bytes: []const u8, | |
| 1038 | stat: File.Stat, | |
| 1039 | ) !void { | |
| 1040 | assert(man.manifest_file != null); | |
| 1041 | const gpa = man.cache.gpa; | |
| 1042 | const prefixed_path = try man.cache.findPrefix(file_path); | |
| 1043 | var keep = false; | |
| 1044 | defer if (!keep) gpa.free(prefixed_path.sub_path); | |
| 1045 | keep = try addPrefixedPathPostContents(man, prefixed_path, bytes, stat); | |
| 1884 | fn populateFile( | |
| 1885 | m: *Manifest, | |
| 1886 | file: *File, | |
| 1887 | need_stat: bool, | |
| 1888 | handle: Io.File, | |
| 1889 | contents: ?[]const u8, | |
| 1890 | metadata_only: bool, | |
| 1891 | diagnostic: ?*AddDiscoveredPathDiagnostic, | |
| 1892 | ) AddDiscoveredPathError!void { | |
| 1893 | const io = m.cache.io; | |
| 1894 | ||
| 1895 | if (need_stat) { | |
| 1896 | const stat = handle.stat(io) catch |err| switch (err) { | |
| 1897 | error.Canceled => |e| return e, | |
| 1898 | else => |e| { | |
| 1899 | if (diagnostic) |d| d.* = .{ .stat_file = e }; | |
| 1900 | return error.FileSystemFailure; | |
| 1901 | }, | |
| 1902 | }; | |
| 1903 | try file.setStat(m, .init(stat)); | |
| 1904 | } | |
| 1905 | if (metadata_only) { | |
| 1906 | file.hashFromMetadata(); | |
| 1907 | return; | |
| 1908 | } | |
| 1909 | if (contents) |bytes| { | |
| 1910 | var hasher = hasher_init; | |
| 1911 | hasher.update(bytes); | |
| 1912 | hasher.final(&file.digest); | |
| 1913 | } else { | |
| 1914 | hashFile(io, handle, &file.digest) catch |err| switch (err) { | |
| 1915 | error.Canceled => |e| return e, | |
| 1916 | else => |e| { | |
| 1917 | if (diagnostic) |d| d.* = .{ .read_file = e }; | |
| 1918 | return error.FileSystemFailure; | |
| 1919 | }, | |
| 1920 | }; | |
| 1921 | } | |
| 1046 | 1922 | } |
| 1047 | 1923 | |
| 1048 | /// Low level function. `prefixed_path` references cloned memory. Returns | |
| 1049 | /// whether or not `prefixed_path.sub_path` should be kept. | |
| 1050 | pub fn addPrefixedPathPostContents( | |
| 1051 | man: *Manifest, | |
| 1052 | prefixed_path: PrefixedPath, | |
| 1053 | bytes: []const u8, | |
| 1054 | stat: File.Stat, | |
| 1055 | ) !bool { | |
| 1056 | const gpa = man.cache.gpa; | |
| 1057 | const gop = try man.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{}); | |
| 1058 | errdefer _ = man.files.pop(); | |
| 1059 | ||
| 1060 | if (gop.found_existing) return false; | |
| 1061 | ||
| 1062 | const new_file = gop.key_ptr; | |
| 1063 | ||
| 1064 | new_file.* = .{ | |
| 1065 | .prefixed_path = prefixed_path, | |
| 1066 | .max_file_size = null, | |
| 1067 | .handle = null, | |
| 1068 | .stat = stat, | |
| 1069 | .bin_digest = undefined, | |
| 1070 | .contents = null, | |
| 1071 | }; | |
| 1072 | ||
| 1073 | if (try man.isProblematicTimestamp(new_file.stat.mtime)) { | |
| 1074 | // The actual file has an unreliable timestamp, force it to be hashed | |
| 1075 | new_file.stat.mtime = .zero; | |
| 1076 | new_file.stat.inode = 0; | |
| 1924 | fn populateDirectory( | |
| 1925 | m: *Manifest, | |
| 1926 | file: *File, | |
| 1927 | need_stat: bool, | |
| 1928 | handle: Io.Dir, | |
| 1929 | contents: ?[]const u8, | |
| 1930 | metadata_only: bool, | |
| 1931 | diagnostic: ?*AddDiscoveredPathDiagnostic, | |
| 1932 | ) AddDiscoveredPathError!void { | |
| 1933 | const cache = m.cache; | |
| 1934 | const io = cache.io; | |
| 1935 | const gpa = cache.gpa; | |
| 1936 | ||
| 1937 | if (need_stat) { | |
| 1938 | const stat = handle.stat(io) catch |err| switch (err) { | |
| 1939 | error.Canceled => |e| return e, | |
| 1940 | else => |e| { | |
| 1941 | if (diagnostic) |d| d.* = .{ .stat_dir = e }; | |
| 1942 | return error.FileSystemFailure; | |
| 1943 | }, | |
| 1944 | }; | |
| 1945 | try file.setStat(m, .init(stat)); | |
| 1077 | 1946 | } |
| 1078 | ||
| 1079 | { | |
| 1947 | if (metadata_only) { | |
| 1948 | file.hashFromMetadata(); | |
| 1949 | return; | |
| 1950 | } | |
| 1951 | if (contents) |bytes| { | |
| 1080 | 1952 | var hasher = hasher_init; |
| 1081 | 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 | } | |
| 1084 | 1967 | |
| 1085 | man.hash.hasher.update(&new_file.bin_digest); | |
| 1086 | return true; | |
| 1968 | /// See also `hitDigestHex`. | |
| 1969 | pub fn hitDigest(m: *Manifest) BinDigest { | |
| 1970 | assert(m.manifest_file != null); | |
| 1971 | assert(m.state == .hit); | |
| 1972 | m.state = .hit_digested; | |
| 1973 | return finalDigest(m); | |
| 1087 | 1974 | } |
| 1088 | 1975 | |
| 1089 | pub fn addDepFilePost(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void { | |
| 1090 | assert(self.manifest_file != null); | |
| 1091 | return self.addDepFileMaybePost(dir, dep_file_sub_path); | |
| 1976 | pub fn hitDigestHex(m: *Manifest) HexDigest { | |
| 1977 | return binToHex(m.hitDigest()); | |
| 1092 | 1978 | } |
| 1093 | 1979 | |
| 1094 | fn addDepFileMaybePost(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void { | |
| 1095 | const gpa = self.cache.gpa; | |
| 1096 | const io = self.cache.io; | |
| 1097 | const dep_file_contents = try dir.readFileAlloc(io, dep_file_sub_path, gpa, .limited(manifest_file_size_max)); | |
| 1098 | defer gpa.free(dep_file_contents); | |
| 1099 | ||
| 1100 | var error_buf: std.ArrayList(u8) = .empty; | |
| 1101 | defer error_buf.deinit(gpa); | |
| 1102 | ||
| 1103 | var resolve_buf: std.ArrayList(u8) = .empty; | |
| 1104 | defer resolve_buf.deinit(gpa); | |
| 1105 | ||
| 1106 | var it: DepTokenizer = .{ .bytes = dep_file_contents }; | |
| 1107 | while (it.next()) |token| { | |
| 1108 | switch (token) { | |
| 1109 | // We don't care about targets, we only want the prereqs | |
| 1110 | // Clang is invoked in single-source mode but other programs may not | |
| 1111 | .target, .target_must_resolve => {}, | |
| 1112 | .prereq => |file_path| if (self.manifest_file == null) { | |
| 1113 | _ = try self.addFilePath(.initCwd(file_path), null); | |
| 1114 | } else try self.addFilePost(file_path), | |
| 1115 | .prereq_must_resolve => { | |
| 1116 | resolve_buf.clearRetainingCapacity(); | |
| 1117 | try token.resolve(gpa, &resolve_buf); | |
| 1118 | if (self.manifest_file == null) { | |
| 1119 | _ = try self.addFilePath(.initCwd(resolve_buf.items), null); | |
| 1120 | } else try self.addFilePost(resolve_buf.items); | |
| 1121 | }, | |
| 1122 | else => |err| { | |
| 1123 | try err.printError(gpa, &error_buf); | |
| 1124 | log.err("failed parsing {s}: {s}", .{ dep_file_sub_path, error_buf.items }); | |
| 1125 | return error.InvalidDepFile; | |
| 1126 | }, | |
| 1127 | } | |
| 1128 | } | |
| 1980 | /// See also `missDigestHex`. | |
| 1981 | pub fn missDigest(m: *Manifest) BinDigest { | |
| 1982 | assert(m.manifest_file != null); | |
| 1983 | transitionToMissDiscovered(m); | |
| 1984 | m.state = .miss_digested; | |
| 1985 | return finalDigest(m); | |
| 1129 | 1986 | } |
| 1130 | 1987 | |
| 1131 | /// Returns a binary hash of the inputs. | |
| 1132 | pub fn finalBin(self: *Manifest) BinDigest { | |
| 1133 | assert(self.manifest_file != null); | |
| 1988 | pub fn missDigestHex(m: *Manifest) HexDigest { | |
| 1989 | return binToHex(m.missDigest()); | |
| 1990 | } | |
| 1134 | 1991 | |
| 1135 | // We don't close the manifest file yet, because we want to | |
| 1136 | // keep it locked until the API user is done using it. | |
| 1137 | // We also don't write out the manifest yet, because until | |
| 1138 | // cache_release is called we still might be working on creating | |
| 1139 | // the artifacts to cache. | |
| 1992 | fn finalDigest(m: *Manifest) BinDigest { | |
| 1993 | const contents = m.contents.items; | |
| 1994 | const hasher = &m.hash.hasher; | |
| 1140 | 1995 | |
| 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 | 2003 | var bin_digest: BinDigest = undefined; |
| 1142 | self.hash.hasher.final(&bin_digest); | |
| 2004 | hasher.final(&bin_digest); | |
| 1143 | 2005 | return bin_digest; |
| 1144 | 2006 | } |
| 1145 | 2007 | |
| 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 | 2008 | /// If `want_shared_lock` is true, this function automatically downgrades the |
| 1153 | 2009 | /// lock from exclusive to shared. |
| 1154 | pub fn writeManifest(self: *Manifest) !void { | |
| 1155 | assert(self.have_exclusive_lock); | |
| 1156 | const io = self.cache.io; | |
| 1157 | const manifest_file = self.manifest_file.?; | |
| 1158 | if (self.manifest_dirty) { | |
| 1159 | self.manifest_dirty = false; | |
| 1160 | ||
| 1161 | var buffer: [4000]u8 = undefined; | |
| 1162 | var fw = manifest_file.writer(io, &buffer); | |
| 1163 | writeDirtyManifestToStream(self, &fw) catch |err| switch (err) { | |
| 1164 | error.WriteFailed => return fw.err.?, | |
| 1165 | else => |e| return e, | |
| 1166 | }; | |
| 1167 | } | |
| 2010 | pub fn finalize(m: *Manifest) !void { | |
| 2011 | const io = m.cache.io; | |
| 2012 | const manifest_file = m.manifest_file.?; | |
| 2013 | ||
| 2014 | assert(m.state == .miss_digested); | |
| 2015 | assert(m.have_exclusive_lock); | |
| 1168 | 2016 | |
| 1169 | if (self.want_shared_lock) { | |
| 1170 | try self.downgradeToSharedLock(); | |
| 2017 | { | |
| 2018 | m.contents.appendAssumeCapacity(0); | |
| 2019 | defer _ = m.contents.pop().?; | |
| 2020 | ||
| 2021 | try manifest_file.setLength(io, m.contents.items.len); | |
| 2022 | try manifest_file.writePositionalAll(io, m.contents.items, 0); | |
| 2023 | ||
| 2024 | m.state = .miss_finalized; | |
| 1171 | 2025 | } |
| 1172 | } | |
| 1173 | 2026 | |
| 1174 | fn writeDirtyManifestToStream(self: *Manifest, fw: *Io.File.Writer) !void { | |
| 1175 | try fw.interface.writeAll(manifest_header ++ "\n"); | |
| 1176 | for (self.files.keys()) |file| { | |
| 1177 | try fw.interface.print("{d} {d} {d} {x} {d} {s}\n", .{ | |
| 1178 | file.stat.size, | |
| 1179 | file.stat.inode, | |
| 1180 | file.stat.mtime, | |
| 1181 | &file.bin_digest, | |
| 1182 | file.prefixed_path.prefix, | |
| 1183 | file.prefixed_path.sub_path, | |
| 1184 | }); | |
| 2027 | if (m.want_shared_lock) { | |
| 2028 | try m.downgradeToSharedLock(); | |
| 1185 | 2029 | } |
| 1186 | try fw.end(); | |
| 1187 | 2030 | } |
| 1188 | 2031 | |
| 1189 | 2032 | fn downgradeToSharedLock(self: *Manifest) !void { |
| ... | ... | @@ -1198,53 +2041,74 @@ pub const Manifest = struct { |
| 1198 | 2041 | self.have_exclusive_lock = false; |
| 1199 | 2042 | } |
| 1200 | 2043 | |
| 1201 | fn upgradeToExclusiveLock(self: *Manifest) error{CacheCheckFailed}!bool { | |
| 1202 | if (self.have_exclusive_lock) return false; | |
| 1203 | assert(self.manifest_file != null); | |
| 1204 | const io = self.cache.io; | |
| 2044 | fn upgradeToExclusiveLock(m: *Manifest, diag: *CheckDiagnostic) error{CacheCheckFailed}!bool { | |
| 2045 | if (m.have_exclusive_lock) return false; | |
| 2046 | assert(m.manifest_file != null); | |
| 2047 | const io = m.cache.io; | |
| 1205 | 2048 | |
| 1206 | 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 | 2051 | // Here we intentionally have a period where the lock is released, in case there are |
| 1209 | 2052 | // other processes holding a shared lock. |
| 1210 | 2053 | manifest_file.unlock(io); |
| 1211 | manifest_file.lock(io, .exclusive) catch |err| { | |
| 1212 | self.diagnostic = .{ .manifest_lock = err }; | |
| 1213 | return error.CacheCheckFailed; | |
| 1214 | }; | |
| 2054 | manifest_file.lock(io, .exclusive) catch |err| return fail(diag, .{ .manifest_lock = err }); | |
| 1215 | 2055 | } |
| 1216 | self.have_exclusive_lock = true; | |
| 2056 | m.have_exclusive_lock = true; | |
| 1217 | 2057 | return true; |
| 1218 | 2058 | } |
| 1219 | 2059 | |
| 1220 | 2060 | /// Obtain only the data needed to maintain a lock on the manifest file. |
| 1221 | 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 | 2064 | pub fn toOwnedLock(self: *Manifest) Lock { |
| 1225 | 2065 | defer self.manifest_file = null; |
| 1226 | 2066 | return .{ .manifest_file = self.manifest_file.? }; |
| 1227 | 2067 | } |
| 1228 | 2068 | |
| 1229 | pub fn takeFiles(man: *Manifest) Files { | |
| 1230 | defer man.files = .empty; | |
| 1231 | return man.files; | |
| 2069 | pub const SelfContainedFiles = struct { | |
| 2070 | /// References memory inside `contents`. | |
| 2071 | files: Files, | |
| 2072 | contents: std.ArrayList(u8), | |
| 2073 | ||
| 2074 | pub const empty: @This() = .{ | |
| 2075 | .files = .empty, | |
| 2076 | .contents = .empty, | |
| 2077 | }; | |
| 2078 | ||
| 2079 | pub fn deinit(scf: *SelfContainedFiles, gpa: Allocator) void { | |
| 2080 | scf.files.deinit(gpa); | |
| 2081 | scf.contents.deinit(gpa); | |
| 2082 | scf.* = undefined; | |
| 2083 | } | |
| 2084 | ||
| 2085 | pub fn path(scf: *const SelfContainedFiles, file_offset: File.Offset) [:0]const u8 { | |
| 2086 | return file_offset.path(scf.contents.items); | |
| 2087 | } | |
| 2088 | }; | |
| 2089 | ||
| 2090 | pub fn takeFiles(m: *Manifest) SelfContainedFiles { | |
| 2091 | defer m.files = .empty; | |
| 2092 | defer m.contents = .empty; | |
| 2093 | return borrowFiles(m); | |
| 1232 | 2094 | } |
| 1233 | 2095 | |
| 1234 | pub fn freeFiles(gpa: Allocator, files: *Files) void { | |
| 1235 | for (files.keys()) |*file| file.deinit(gpa); | |
| 1236 | files.deinit(gpa); | |
| 2096 | pub fn borrowFiles(m: *const Manifest) SelfContainedFiles { | |
| 2097 | return .{ | |
| 2098 | .files = m.files, | |
| 2099 | .contents = m.contents, | |
| 2100 | }; | |
| 1237 | 2101 | } |
| 1238 | 2102 | |
| 1239 | 2103 | /// Releases the manifest file and frees any memory the Manifest was using. |
| 1240 | 2104 | /// `Manifest.hit` must be called first. |
| 1241 | 2105 | /// |
| 1242 | /// Don't forget to call `writeManifest` before this! | |
| 1243 | pub fn deinit(man: *Manifest) void { | |
| 1244 | const io = man.cache.io; | |
| 1245 | const gpa = man.cache.gpa; | |
| 2106 | /// Don't forget to call `finalize` before this! | |
| 2107 | pub fn deinit(m: *Manifest) void { | |
| 2108 | const io = m.cache.io; | |
| 2109 | const gpa = m.cache.gpa; | |
| 1246 | 2110 | |
| 1247 | if (man.manifest_file) |file| { | |
| 2111 | if (m.manifest_file) |file| { | |
| 1248 | 2112 | if (builtin.os.tag == .windows) { |
| 1249 | 2113 | // See Lock.release for why this is required on Windows |
| 1250 | 2114 | file.unlock(io); |
| ... | ... | @@ -1252,20 +2116,25 @@ pub const Manifest = struct { |
| 1252 | 2116 | |
| 1253 | 2117 | file.close(io); |
| 1254 | 2118 | } |
| 1255 | freeFiles(gpa, &man.files); | |
| 1256 | man.* = undefined; | |
| 2119 | m.files.deinit(gpa); | |
| 2120 | m.contents.deinit(gpa); | |
| 2121 | m.input_paths.deinit(gpa); | |
| 2122 | m.all_input_content.deinit(gpa); | |
| 2123 | m.* = undefined; | |
| 1257 | 2124 | } |
| 1258 | 2125 | |
| 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 | 2127 | assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == man.cache.prefixes_len); |
| 1261 | 2128 | buf.clearRetainingCapacity(); |
| 1262 | 2129 | const gpa = man.cache.gpa; |
| 1263 | 2130 | const files = man.files.keys(); |
| 1264 | 2131 | if (files.len > 0) { |
| 2132 | const contents = man.contents.items; | |
| 1265 | 2133 | for (files) |file| { |
| 1266 | try buf.ensureUnusedCapacity(gpa, file.prefixed_path.sub_path.len + 2); | |
| 1267 | buf.appendAssumeCapacity(file.prefixed_path.prefix + 1); | |
| 1268 | buf.appendSliceAssumeCapacity(file.prefixed_path.sub_path); | |
| 2134 | const file_path = file.path(contents); | |
| 2135 | try buf.ensureUnusedCapacity(gpa, file_path.len + 2); | |
| 2136 | buf.appendAssumeCapacity(@as(u8, file.get(contents).flags.prefix) + 1); | |
| 2137 | buf.appendSliceAssumeCapacity(file_path); | |
| 1269 | 2138 | buf.appendAssumeCapacity(0); |
| 1270 | 2139 | } |
| 1271 | 2140 | // The null byte is a separator, not a terminator. |
| ... | ... | @@ -1273,53 +2142,157 @@ pub const Manifest = struct { |
| 1273 | 2142 | } |
| 1274 | 2143 | } |
| 1275 | 2144 | |
| 1276 | pub fn populateOtherManifest(man: *Manifest, other: *Manifest, prefix_map: [5]u8) Allocator.Error!void { | |
| 1277 | const gpa = other.cache.gpa; | |
| 1278 | assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == man.cache.prefixes_len); | |
| 1279 | assert(man.cache.prefixes_len == 5); | |
| 1280 | for (man.files.keys()) |file| { | |
| 1281 | const prefixed_path: PrefixedPath = .{ | |
| 1282 | .prefix = prefix_map[file.prefixed_path.prefix], | |
| 1283 | .sub_path = try gpa.dupe(u8, file.prefixed_path.sub_path), | |
| 1284 | }; | |
| 1285 | errdefer gpa.free(prefixed_path.sub_path); | |
| 2145 | /// Add the full set of paths from another `Manifest` as a dependency of process being cached, after cache miss | |
| 2146 | /// occurs. | |
| 2147 | /// | |
| 2148 | /// See also: | |
| 2149 | /// * `addDiscoveredPath | |
| 2150 | /// * `addDiscoveredDepFile` | |
| 2151 | pub fn addDiscoveredManifest(m: *Manifest, discovered: *const Manifest, prefix_map: [5]u8) Allocator.Error!void { | |
| 2152 | const gpa = m.cache.gpa; | |
| 2153 | assert(m.manifest_file != null); | |
| 2154 | transitionToMissDiscovered(m); | |
| 2155 | assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == discovered.cache.prefixes_len); | |
| 2156 | assert(discovered.cache.prefixes_len == 5); | |
| 2157 | ||
| 2158 | const discovered_files = discovered.files.keys(); | |
| 2159 | const orig_files_len = m.files.count(); | |
| 2160 | const orig_contents_len = m.contents.items.len; | |
| 2161 | errdefer { | |
| 2162 | m.files.shrinkRetainingCapacityContext(orig_files_len, .{ .contents = m.contents.items }); | |
| 2163 | m.contents.shrinkRetainingCapacity(orig_contents_len); | |
| 2164 | } | |
| 2165 | ||
| 2166 | for (discovered_files, 0..) |off, file_index| { | |
| 2167 | try m.files.ensureUnusedCapacityContext(gpa, 1, .{ .contents = m.contents.items }); | |
| 2168 | ||
| 2169 | const next_index = file_index + 1; | |
| 2170 | const next_off = if (discovered_files.len - next_index == 0) | |
| 2171 | discovered.contents.items.len | |
| 2172 | else | |
| 2173 | @backingInt(discovered_files[next_index]); | |
| 1286 | 2174 | |
| 1287 | const gop = try other.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{}); | |
| 1288 | errdefer _ = other.files.pop(); | |
| 2175 | const copy_bytes = discovered.contents.items[@backingInt(off)..next_off]; | |
| 2176 | const prev_contents_len: File.Offset = @fromBackingInt(@intCast(m.contents.items.len)); | |
| 2177 | try m.contents.appendSlice(gpa, copy_bytes); | |
| 2178 | ||
| 2179 | const gop = m.files.getOrPutAssumeCapacityContext(prev_contents_len, .{ | |
| 2180 | .contents = m.contents.items, | |
| 2181 | }); | |
| 1289 | 2182 | |
| 1290 | 2183 | if (gop.found_existing) { |
| 1291 | gpa.free(prefixed_path.sub_path); | |
| 2184 | m.contents.shrinkRetainingCapacity(@backingInt(prev_contents_len)); | |
| 1292 | 2185 | continue; |
| 1293 | 2186 | } |
| 1294 | 2187 | |
| 1295 | gop.key_ptr.* = .{ | |
| 1296 | .prefixed_path = prefixed_path, | |
| 1297 | .max_file_size = file.max_file_size, | |
| 1298 | .handle = file.handle, | |
| 1299 | .stat = file.stat, | |
| 1300 | .bin_digest = file.bin_digest, | |
| 1301 | .contents = null, | |
| 1302 | }; | |
| 2188 | // Flags are already copied but the prefix is supposed to be filtered by `prefix_map`. | |
| 2189 | const other_file = prev_contents_len.get(m.contents.items); | |
| 2190 | other_file.flags.prefix = @intCast(prefix_map[other_file.flags.prefix]); | |
| 2191 | } | |
| 2192 | } | |
| 1303 | 2193 | |
| 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 | }; | |
| 1308 | 2206 | |
| 1309 | fn hashFile(io: Io, file: Io.File, bin_digest: *[Hasher.mac_length]u8) Io.File.ReadPositionalError!void { | |
| 1310 | var buffer: [2048]u8 = undefined; | |
| 1311 | var hasher = hasher_init; | |
| 1312 | var offset: u64 = 0; | |
| 1313 | while (true) { | |
| 1314 | const n = try file.readPositional(io, &.{&buffer}, offset); | |
| 1315 | if (n == 0) break; | |
| 1316 | hasher.update(buffer[0..n]); | |
| 1317 | offset += n; | |
| 2207 | fn hashFileAppend( | |
| 2208 | io: Io, | |
| 2209 | file: Io.File, | |
| 2210 | bin_digest: *[Hasher.mac_length]u8, | |
| 2211 | al: *std.ArrayList(u8), | |
| 2212 | gpa: Allocator, | |
| 2213 | ) (Io.File.ReadPositionalError || Allocator.Error)!void { | |
| 2214 | var hasher = hasher_init; | |
| 2215 | var offset: u64 = 0; | |
| 2216 | while (true) { | |
| 2217 | try al.ensureUnusedCapacity(gpa, 2048); | |
| 2218 | const buffer = al.unusedCapacitySlice(); | |
| 2219 | const n = try file.readPositional(io, &.{buffer}, offset); | |
| 2220 | if (n == 0) break; | |
| 2221 | hasher.update(buffer[0..n]); | |
| 2222 | offset += n; | |
| 2223 | al.items.len += n; | |
| 2224 | } | |
| 2225 | hasher.final(bin_digest); | |
| 1318 | 2226 | } |
| 1319 | hasher.final(bin_digest); | |
| 1320 | } | |
| 1321 | 2227 | |
| 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. | |
| 1323 | 2296 | fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp { |
| 1324 | 2297 | const test_out_file = "test-filetimestamp.tmp"; |
| 1325 | 2298 | |
| ... | ... | @@ -1348,11 +2321,16 @@ test "cache file and then recall it" { |
| 1348 | 2321 | const temp_manifest_dir = "temp_manifest_dir"; |
| 1349 | 2322 | |
| 1350 | 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.?); | |
| 1351 | 2329 | |
| 1352 | 2330 | // Wait for file timestamps to tick |
| 1353 | 2331 | const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir); |
| 1354 | 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 | } |
| 1357 | 2335 | |
| 1358 | 2336 | var digest1: HexDigest = undefined; |
| ... | ... | @@ -1365,45 +2343,53 @@ test "cache file and then recall it" { |
| 1365 | 2343 | .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}), |
| 1366 | 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 | 2348 | defer cache.manifest_dir.close(io); |
| 1370 | 2349 | |
| 1371 | 2350 | { |
| 1372 | var ch = cache.obtain(); | |
| 1373 | defer ch.deinit(); | |
| 1374 | ||
| 1375 | ch.hash.add(true); | |
| 1376 | ch.hash.add(@as(u16, 1234)); | |
| 1377 | ch.hash.addBytes("1234"); | |
| 1378 | _ = try ch.addFilePath(.initCwd(temp_file), null); | |
| 1379 | ||
| 1380 | // There should be nothing in the cache | |
| 1381 | try testing.expectEqual(false, try ch.hit(.none)); | |
| 1382 | ||
| 1383 | digest1 = ch.final(); | |
| 1384 | try ch.writeManifest(); | |
| 2351 | var man = cache.obtain(); | |
| 2352 | defer man.deinit(); | |
| 2353 | ||
| 2354 | man.hash.add(true); | |
| 2355 | man.hash.add(@as(u16, 1234)); | |
| 2356 | man.hash.addBytes("1234"); | |
| 2357 | _ = try man.addInputPath(.{ | |
| 2358 | .root_dir = tmp_directory, | |
| 2359 | .sub_path = temp_file, | |
| 2360 | }, .{}); | |
| 2361 | ||
| 2362 | var diag: Manifest.CheckDiagnostic = undefined; | |
| 2363 | try testing.expectEqual(.incomplete_manifest, try man.check(&diag, .none)); | |
| 2364 | ||
| 2365 | digest1 = man.missDigestHex(); | |
| 2366 | try man.finalize(); | |
| 1385 | 2367 | } |
| 1386 | 2368 | { |
| 1387 | var ch = cache.obtain(); | |
| 1388 | defer ch.deinit(); | |
| 2369 | var man = cache.obtain(); | |
| 2370 | defer man.deinit(); | |
| 1389 | 2371 | |
| 1390 | ch.hash.add(true); | |
| 1391 | ch.hash.add(@as(u16, 1234)); | |
| 1392 | ch.hash.addBytes("1234"); | |
| 1393 | _ = try ch.addFilePath(.initCwd(temp_file), null); | |
| 2372 | man.hash.add(true); | |
| 2373 | man.hash.add(@as(u16, 1234)); | |
| 2374 | man.hash.addBytes("1234"); | |
| 2375 | _ = try man.addInputPath(.{ | |
| 2376 | .root_dir = tmp_directory, | |
| 2377 | .sub_path = temp_file, | |
| 2378 | }, .{}); | |
| 1394 | 2379 | |
| 1395 | 2380 | // Cache hit! We just "built" the same file |
| 1396 | try testing.expect(try ch.hit(.none)); | |
| 1397 | digest2 = ch.final(); | |
| 2381 | var diag: Manifest.CheckDiagnostic = undefined; | |
| 2382 | try testing.expectEqual(.hit, try man.check(&diag, .none)); | |
| 2383 | digest2 = man.hitDigestHex(); | |
| 1398 | 2384 | |
| 1399 | try testing.expectEqual(false, ch.have_exclusive_lock); | |
| 2385 | try testing.expectEqual(false, man.have_exclusive_lock); | |
| 1400 | 2386 | } |
| 1401 | 2387 | |
| 1402 | 2388 | try testing.expectEqual(digest1, digest2); |
| 1403 | 2389 | } |
| 1404 | 2390 | } |
| 1405 | 2391 | |
| 1406 | test "check that changing a file makes cache fail" { | |
| 2392 | test "check that changing a file causes cache miss" { | |
| 1407 | 2393 | const io = testing.io; |
| 1408 | 2394 | |
| 1409 | 2395 | var tmp = testing.tmpDir(.{}); |
| ... | ... | @@ -1418,11 +2404,16 @@ test "check that changing a file makes cache fail" { |
| 1418 | 2404 | const updated_temp_file_contents = "Hello, world; but updated!\n"; |
| 1419 | 2405 | |
| 1420 | 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.?); | |
| 1421 | 2412 | |
| 1422 | 2413 | // Wait for file timestamps to tick |
| 1423 | 2414 | const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir); |
| 1424 | 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 | } |
| 1427 | 2418 | |
| 1428 | 2419 | var digest1: HexDigest = undefined; |
| ... | ... | @@ -1435,47 +2426,57 @@ test "check that changing a file makes cache fail" { |
| 1435 | 2426 | .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}), |
| 1436 | 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 | 2431 | defer cache.manifest_dir.close(io); |
| 1440 | 2432 | |
| 1441 | 2433 | { |
| 1442 | var ch = cache.obtain(); | |
| 1443 | defer ch.deinit(); | |
| 2434 | var man = cache.obtain(); | |
| 2435 | defer man.deinit(); | |
| 1444 | 2436 | |
| 1445 | ch.hash.addBytes("1234"); | |
| 1446 | const temp_file_idx = try ch.addFilePath(.initCwd(temp_file), 100); | |
| 2437 | man.hash.addBytes("1234"); | |
| 2438 | const temp_file_idx = try man.addInputPath(.{ | |
| 2439 | .root_dir = tmp_directory, | |
| 2440 | .sub_path = temp_file, | |
| 2441 | }, .{ .request_contents = true }); | |
| 1447 | 2442 | |
| 1448 | // There should be nothing in the cache | |
| 1449 | try testing.expectEqual(false, try ch.hit(.none)); | |
| 2443 | var diag: Manifest.CheckDiagnostic = undefined; | |
| 2444 | try testing.expectEqual(.incomplete_manifest, try man.check(&diag, .none)); | |
| 1450 | 2445 | |
| 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)); | |
| 1452 | 2447 | |
| 1453 | digest1 = ch.final(); | |
| 2448 | digest1 = man.missDigestHex(); | |
| 1454 | 2449 | |
| 1455 | try ch.writeManifest(); | |
| 2450 | try man.finalize(); | |
| 1456 | 2451 | } |
| 1457 | 2452 | |
| 1458 | 2453 | try tmp.dir.writeFile(io, .{ .sub_path = temp_file, .data = updated_temp_file_contents }); |
| 1459 | 2454 | |
| 1460 | 2455 | { |
| 1461 | var ch = cache.obtain(); | |
| 1462 | defer ch.deinit(); | |
| 2456 | var man = cache.obtain(); | |
| 2457 | defer man.deinit(); | |
| 1463 | 2458 | |
| 1464 | ch.hash.addBytes("1234"); | |
| 1465 | const temp_file_idx = try ch.addFilePath(.initCwd(temp_file), 100); | |
| 2459 | man.hash.addBytes("1234"); | |
| 2460 | const temp_file_idx = try man.addInputPath(.{ | |
| 2461 | .root_dir = tmp_directory, | |
| 2462 | .sub_path = temp_file, | |
| 2463 | }, .{ .request_contents = true }); | |
| 1466 | 2464 | |
| 1467 | // A file that we depend on has been updated, so the cache should not contain an entry for it | |
| 1468 | try testing.expectEqual(false, try ch.hit(.none)); | |
| 2465 | // The one input file changed. | |
| 2466 | var diag: Manifest.CheckDiagnostic = undefined; | |
| 2467 | try testing.expectEqual( | |
| 2468 | @as(Manifest.CheckResult, .{ .contents_changed = temp_file_idx.offset(&man) }), | |
| 2469 | try man.check(&diag, .none), | |
| 2470 | ); | |
| 1469 | 2471 | |
| 1470 | // The cache system does not keep the contents of re-hashed input files. | |
| 1471 | try testing.expect(ch.files.keys()[temp_file_idx].contents == null); | |
| 2472 | try testing.expectEqualStrings(updated_temp_file_contents, temp_file_idx.contents(&man)); | |
| 1472 | 2473 | |
| 1473 | digest2 = ch.final(); | |
| 2474 | digest2 = man.missDigestHex(); | |
| 1474 | 2475 | |
| 1475 | try ch.writeManifest(); | |
| 2476 | try man.finalize(); | |
| 1476 | 2477 | } |
| 1477 | 2478 | |
| 1478 | try testing.expect(!mem.eql(u8, digest1[0..], digest2[0..])); | |
| 2479 | try testing.expect(!mem.eql(u8, &digest1, &digest2)); | |
| 1479 | 2480 | } |
| 1480 | 2481 | } |
| 1481 | 2482 | |
| ... | ... | @@ -1508,12 +2509,12 @@ test "no file inputs" { |
| 1508 | 2509 | |
| 1509 | 2510 | man.hash.addBytes("1234"); |
| 1510 | 2511 | |
| 1511 | // There should be nothing in the cache | |
| 1512 | try testing.expectEqual(false, try man.hit(.none)); | |
| 2512 | var diag: Manifest.CheckDiagnostic = undefined; | |
| 2513 | try testing.expectEqual(.incomplete_manifest, try man.check(&diag, .none)); | |
| 1513 | 2514 | |
| 1514 | digest1 = man.final(); | |
| 2515 | digest1 = man.missDigestHex(); | |
| 1515 | 2516 | |
| 1516 | try man.writeManifest(); | |
| 2517 | try man.finalize(); | |
| 1517 | 2518 | } |
| 1518 | 2519 | { |
| 1519 | 2520 | var man = cache.obtain(); |
| ... | ... | @@ -1521,15 +2522,16 @@ test "no file inputs" { |
| 1521 | 2522 | |
| 1522 | 2523 | man.hash.addBytes("1234"); |
| 1523 | 2524 | |
| 1524 | try testing.expect(try man.hit(.none)); | |
| 1525 | digest2 = man.final(); | |
| 2525 | var diag: Manifest.CheckDiagnostic = undefined; | |
| 2526 | try testing.expectEqual(.hit, try man.check(&diag, .none)); | |
| 2527 | digest2 = man.hitDigestHex(); | |
| 1526 | 2528 | try testing.expectEqual(false, man.have_exclusive_lock); |
| 1527 | 2529 | } |
| 1528 | 2530 | |
| 1529 | 2531 | try testing.expectEqual(digest1, digest2); |
| 1530 | 2532 | } |
| 1531 | 2533 | |
| 1532 | test "Manifest with files added after initial hash work" { | |
| 2534 | test "Manifest with files added after initial hash" { | |
| 1533 | 2535 | const io = testing.io; |
| 1534 | 2536 | |
| 1535 | 2537 | var tmp = testing.tmpDir(.{}); |
| ... | ... | @@ -1544,11 +2546,16 @@ test "Manifest with files added after initial hash work" { |
| 1544 | 2546 | |
| 1545 | 2547 | try tmp.dir.writeFile(io, .{ .sub_path = temp_file1, .data = "Hello, world!\n" }); |
| 1546 | 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.?); | |
| 1547 | 2554 | |
| 1548 | 2555 | // Wait for file timestamps to tick |
| 1549 | 2556 | const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir); |
| 1550 | 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 | } |
| 1553 | 2560 | |
| 1554 | 2561 | var digest1: HexDigest = undefined; |
| ... | ... | @@ -1562,35 +2569,46 @@ test "Manifest with files added after initial hash work" { |
| 1562 | 2569 | .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}), |
| 1563 | 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 | 2574 | defer cache.manifest_dir.close(io); |
| 1567 | 2575 | |
| 1568 | 2576 | { |
| 1569 | var ch = cache.obtain(); | |
| 1570 | defer ch.deinit(); | |
| 2577 | var man = cache.obtain(); | |
| 2578 | defer man.deinit(); | |
| 1571 | 2579 | |
| 1572 | ch.hash.addBytes("1234"); | |
| 1573 | _ = try ch.addFilePath(.initCwd(temp_file1), null); | |
| 2580 | man.hash.addBytes("1234"); | |
| 2581 | _ = try man.addInputPath(.{ | |
| 2582 | .root_dir = tmp_directory, | |
| 2583 | .sub_path = temp_file1, | |
| 2584 | }, .{}); | |
| 1574 | 2585 | |
| 1575 | // There should be nothing in the cache | |
| 1576 | try testing.expectEqual(false, try ch.hit(.none)); | |
| 2586 | var diag: Manifest.CheckDiagnostic = undefined; | |
| 2587 | try testing.expectEqual(.incomplete_manifest, try man.check(&diag, .none)); | |
| 1577 | 2588 | |
| 1578 | _ = try ch.addFilePost(temp_file2); | |
| 2589 | try man.addDiscoveredPath(.{ .discovered_path = .{ .unresolved = .{ | |
| 2590 | .root_dir = tmp_directory, | |
| 2591 | .sub_path = temp_file2, | |
| 2592 | } } }); | |
| 1579 | 2593 | |
| 1580 | digest1 = ch.final(); | |
| 1581 | try ch.writeManifest(); | |
| 2594 | digest1 = man.missDigestHex(); | |
| 2595 | try man.finalize(); | |
| 1582 | 2596 | } |
| 1583 | 2597 | { |
| 1584 | var ch = cache.obtain(); | |
| 1585 | defer ch.deinit(); | |
| 2598 | var man = cache.obtain(); | |
| 2599 | defer man.deinit(); | |
| 1586 | 2600 | |
| 1587 | ch.hash.addBytes("1234"); | |
| 1588 | _ = try ch.addFilePath(.initCwd(temp_file1), null); | |
| 2601 | man.hash.addBytes("1234"); | |
| 2602 | _ = try man.addInputPath(.{ | |
| 2603 | .root_dir = tmp_directory, | |
| 2604 | .sub_path = temp_file1, | |
| 2605 | }, .{}); | |
| 1589 | 2606 | |
| 1590 | try testing.expect(try ch.hit(.none)); | |
| 1591 | digest2 = ch.final(); | |
| 2607 | var diag: Manifest.CheckDiagnostic = undefined; | |
| 2608 | try testing.expect(.hit == try man.check(&diag, .none)); | |
| 2609 | digest2 = man.hitDigestHex(); | |
| 1592 | 2610 | |
| 1593 | try testing.expectEqual(false, ch.have_exclusive_lock); | |
| 2611 | try testing.expectEqual(false, man.have_exclusive_lock); | |
| 1594 | 2612 | } |
| 1595 | 2613 | try testing.expect(mem.eql(u8, &digest1, &digest2)); |
| 1596 | 2614 | |
| ... | ... | @@ -1600,26 +2618,43 @@ test "Manifest with files added after initial hash work" { |
| 1600 | 2618 | // Wait for file timestamps to tick |
| 1601 | 2619 | const initial_time2 = try testGetCurrentFileTimestamp(io, tmp.dir); |
| 1602 | 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 | } |
| 1605 | 2623 | |
| 1606 | 2624 | { |
| 1607 | var ch = cache.obtain(); | |
| 1608 | defer ch.deinit(); | |
| 1609 | ||
| 1610 | ch.hash.addBytes("1234"); | |
| 1611 | _ = try ch.addFilePath(.initCwd(temp_file1), null); | |
| 1612 | ||
| 1613 | // A file that we depend on has been updated, so the cache should not contain an entry for it | |
| 1614 | try testing.expectEqual(false, try ch.hit(.none)); | |
| 2625 | var man = cache.obtain(); | |
| 2626 | defer man.deinit(); | |
| 2627 | ||
| 2628 | man.hash.addBytes("1234"); | |
| 2629 | _ = try man.addInputPath(.{ | |
| 2630 | .root_dir = tmp_directory, | |
| 2631 | .sub_path = temp_file1, | |
| 2632 | }, .{}); | |
| 2633 | ||
| 2634 | var diag: Manifest.CheckDiagnostic = undefined; | |
| 2635 | switch (try man.check(&diag, .none)) { | |
| 2636 | .contents_changed => |off| { | |
| 2637 | try testing.expectEqualStrings(temp_file2, off.path(man.contents.items)); | |
| 2638 | }, | |
| 2639 | else => return error.TestFailed, | |
| 2640 | } | |
| 1615 | 2641 | |
| 1616 | _ = try ch.addFilePost(temp_file2); | |
| 2642 | try man.addDiscoveredPath(.{ .discovered_path = .{ .unresolved = .{ | |
| 2643 | .root_dir = tmp_directory, | |
| 2644 | .sub_path = temp_file2, | |
| 2645 | } } }); | |
| 1617 | 2646 | |
| 1618 | digest3 = ch.final(); | |
| 2647 | digest3 = man.missDigestHex(); | |
| 1619 | 2648 | |
| 1620 | try ch.writeManifest(); | |
| 2649 | try man.finalize(); | |
| 1621 | 2650 | } |
| 1622 | 2651 | |
| 1623 | 2652 | try testing.expect(!mem.eql(u8, &digest1, &digest3)); |
| 1624 | 2653 | } |
| 1625 | 2654 | } |
| 2655 | ||
| 2656 | test { | |
| 2657 | _ = Path; | |
| 2658 | _ = Directory; | |
| 2659 | _ = DepTokenizer; | |
| 2660 | } |
lib/std/Build/Cache/DepTokenizer.zig+188-250| ... | ... | @@ -1,14 +1,15 @@ |
| 1 | 1 | const Tokenizer = @This(); |
| 2 | 2 | |
| 3 | index: usize = 0, | |
| 4 | bytes: []const u8, | |
| 5 | state: State = .lhs, | |
| 6 | ||
| 7 | 3 | const std = @import("std"); |
| 4 | const Io = std.Io; | |
| 8 | 5 | const testing = std.testing; |
| 9 | 6 | const assert = std.debug.assert; |
| 10 | 7 | const Allocator = std.mem.Allocator; |
| 11 | 8 | |
| 9 | index: usize = 0, | |
| 10 | bytes: []const u8, | |
| 11 | state: State = .lhs, | |
| 12 | ||
| 12 | 13 | pub fn next(self: *Tokenizer) ?Token { |
| 13 | 14 | var start = self.index; |
| 14 | 15 | var must_resolve = false; |
| ... | ... | @@ -16,10 +17,7 @@ pub fn next(self: *Tokenizer) ?Token { |
| 16 | 17 | const char = self.bytes[self.index]; |
| 17 | 18 | switch (self.state) { |
| 18 | 19 | .lhs => switch (char) { |
| 19 | '\t', '\n', '\r', ' ' => { | |
| 20 | // silently ignore whitespace | |
| 21 | self.index += 1; | |
| 22 | }, | |
| 20 | '\t', '\n', '\r', ' ' => self.index += 1, // Ignore whitespace. | |
| 23 | 21 | else => { |
| 24 | 22 | start = self.index; |
| 25 | 23 | self.state = .target; |
| ... | ... | @@ -79,7 +77,7 @@ pub fn next(self: *Tokenizer) ?Token { |
| 79 | 77 | self.index += 1; |
| 80 | 78 | }, |
| 81 | 79 | else => { |
| 82 | return errorIllegalChar(.expected_dollar_sign, self.index, char); | |
| 80 | return errorIllegalChar(.expected_dollar, self.index, char); | |
| 83 | 81 | }, |
| 84 | 82 | }, |
| 85 | 83 | .target_colon => switch (char) { |
| ... | ... | @@ -347,7 +345,7 @@ pub const Token = union(enum) { |
| 347 | 345 | |
| 348 | 346 | invalid_target: IndexAndChar, |
| 349 | 347 | bad_target_escape: IndexAndChar, |
| 350 | expected_dollar_sign: IndexAndChar, | |
| 348 | expected_dollar: IndexAndChar, | |
| 351 | 349 | continuation_eol: IndexAndChar, |
| 352 | 350 | incomplete_escape: IndexAndChar, |
| 353 | 351 | expected_colon: IndexAndChar, |
| ... | ... | @@ -429,48 +427,23 @@ pub const Token = union(enum) { |
| 429 | 427 | } |
| 430 | 428 | } |
| 431 | 429 | |
| 432 | pub fn printError(self: Token, gpa: Allocator, list: *std.ArrayList(u8)) error{OutOfMemory}!void { | |
| 433 | switch (self) { | |
| 434 | .target, .target_must_resolve, .prereq, .prereq_must_resolve => unreachable, // not an error | |
| 435 | .incomplete_quoted_prerequisite, | |
| 436 | .incomplete_target, | |
| 437 | => |index_and_bytes| { | |
| 438 | try list.print(gpa, "{s} '", .{self.errStr()}); | |
| 439 | if (self == .incomplete_target) { | |
| 440 | const tmp = Token{ .target_must_resolve = index_and_bytes.bytes }; | |
| 441 | try tmp.resolve(gpa, list); | |
| 442 | } else { | |
| 443 | try printCharValues(gpa, list, index_and_bytes.bytes); | |
| 444 | } | |
| 445 | try list.print(gpa, "' at position {d}", .{index_and_bytes.index}); | |
| 446 | }, | |
| 430 | pub fn format(token: Token, w: *Io.Writer) Io.Writer.Error!void { | |
| 431 | switch (token) { | |
| 432 | .target, .target_must_resolve, .prereq, .prereq_must_resolve => return w.writeAll(@tagName(token)), | |
| 433 | .incomplete_quoted_prerequisite, .incomplete_target => |info| return w.print("{t} {q} at position {d}", .{ | |
| 434 | token, info.bytes, info.index, | |
| 435 | }), | |
| 447 | 436 | .invalid_target, |
| 448 | 437 | .bad_target_escape, |
| 449 | .expected_dollar_sign, | |
| 438 | .expected_dollar, | |
| 450 | 439 | .continuation_eol, |
| 451 | 440 | .incomplete_escape, |
| 452 | 441 | .expected_colon, |
| 453 | => |index_and_char| { | |
| 454 | try list.appendSlice(gpa, "illegal char "); | |
| 455 | try printUnderstandableChar(gpa, list, index_and_char.char); | |
| 456 | try list.print(gpa, " at position {d}: {s}", .{ index_and_char.index, self.errStr() }); | |
| 457 | }, | |
| 442 | => |info| return w.print("illegal char '{f}' at position {d}: {t}", .{ | |
| 443 | std.zig.fmtChar(info.char), info.index, token, | |
| 444 | }), | |
| 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 | }; |
| 475 | 448 | |
| 476 | 449 | test "empty file" { |
| ... | ... | @@ -493,37 +466,37 @@ test "empty colon" { |
| 493 | 466 | } |
| 494 | 467 | |
| 495 | 468 | test "empty target" { |
| 496 | try depTokenizer("foo.o:", "target = {foo.o}"); | |
| 469 | try depTokenizer("foo.o:", "target = {\"foo.o\"}"); | |
| 497 | 470 | try depTokenizer( |
| 498 | 471 | \\foo.o: |
| 499 | 472 | \\bar.o: |
| 500 | 473 | \\abcd.o: |
| 501 | 474 | , |
| 502 | \\target = {foo.o} | |
| 503 | \\target = {bar.o} | |
| 504 | \\target = {abcd.o} | |
| 475 | \\target = {"foo.o"} | |
| 476 | \\target = {"bar.o"} | |
| 477 | \\target = {"abcd.o"} | |
| 505 | 478 | ); |
| 506 | 479 | } |
| 507 | 480 | |
| 508 | 481 | test "whitespace empty target" { |
| 509 | try depTokenizer("\nfoo.o:", "target = {foo.o}"); | |
| 510 | try depTokenizer("\rfoo.o:", "target = {foo.o}"); | |
| 511 | try depTokenizer("\r\nfoo.o:", "target = {foo.o}"); | |
| 512 | try depTokenizer(" foo.o:", "target = {foo.o}"); | |
| 482 | try depTokenizer("\nfoo.o:", "target = {\"foo.o\"}"); | |
| 483 | try depTokenizer("\rfoo.o:", "target = {\"foo.o\"}"); | |
| 484 | try depTokenizer("\r\nfoo.o:", "target = {\"foo.o\"}"); | |
| 485 | try depTokenizer(" foo.o:", "target = {\"foo.o\"}"); | |
| 513 | 486 | } |
| 514 | 487 | |
| 515 | 488 | test "escape empty target" { |
| 516 | try depTokenizer("\\ foo.o:", "target = { foo.o}"); | |
| 517 | try depTokenizer("\\#foo.o:", "target = {#foo.o}"); | |
| 518 | try depTokenizer("\\\\foo.o:", "target = {\\foo.o}"); | |
| 519 | try depTokenizer("$$foo.o:", "target = {$foo.o}"); | |
| 489 | try depTokenizer("\\ foo.o:", "target = {\" foo.o\"}"); | |
| 490 | try depTokenizer("\\#foo.o:", "target = {\"#foo.o\"}"); | |
| 491 | try depTokenizer("\\\\foo.o:", "target = {\"\\\\foo.o\"}"); | |
| 492 | try depTokenizer("$$foo.o:", "target = {\"$foo.o\"}"); | |
| 520 | 493 | } |
| 521 | 494 | |
| 522 | 495 | test "empty target linefeeds" { |
| 523 | 496 | try depTokenizer("\n", ""); |
| 524 | 497 | try depTokenizer("\r\n", ""); |
| 525 | 498 | |
| 526 | const expect = "target = {foo.o}"; | |
| 499 | const expect = "target = {\"foo.o\"}"; | |
| 527 | 500 | try depTokenizer( |
| 528 | 501 | \\foo.o: |
| 529 | 502 | , expect); |
| ... | ... | @@ -541,7 +514,7 @@ test "empty target linefeeds" { |
| 541 | 514 | } |
| 542 | 515 | |
| 543 | 516 | test "empty target linefeeds + continuations" { |
| 544 | const expect = "target = {foo.o}"; | |
| 517 | const expect = "target = {\"foo.o\"}"; | |
| 545 | 518 | try depTokenizer( |
| 546 | 519 | \\foo.o:\ |
| 547 | 520 | , expect); |
| ... | ... | @@ -559,7 +532,7 @@ test "empty target linefeeds + continuations" { |
| 559 | 532 | } |
| 560 | 533 | |
| 561 | 534 | test "empty target linefeeds + hspace + continuations" { |
| 562 | const expect = "target = {foo.o}"; | |
| 535 | const expect = "target = {\"foo.o\"}"; | |
| 563 | 536 | try depTokenizer( |
| 564 | 537 | \\foo.o: \ |
| 565 | 538 | , expect); |
| ... | ... | @@ -577,7 +550,7 @@ test "empty target linefeeds + hspace + continuations" { |
| 577 | 550 | } |
| 578 | 551 | |
| 579 | 552 | test "empty target + hspace + colon" { |
| 580 | const expect = "target = {foo.o}"; | |
| 553 | const expect = "target = {\"foo.o\"}"; | |
| 581 | 554 | |
| 582 | 555 | try depTokenizer("foo.o :", expect); |
| 583 | 556 | try depTokenizer("foo.o\t\t\t:", expect); |
| ... | ... | @@ -588,8 +561,8 @@ test "empty target + hspace + colon" { |
| 588 | 561 | |
| 589 | 562 | test "prereq" { |
| 590 | 563 | const expect = |
| 591 | \\target = {foo.o} | |
| 592 | \\prereq = {foo.c} | |
| 564 | \\target = {"foo.o"} | |
| 565 | \\prereq = {"foo.c"} | |
| 593 | 566 | ; |
| 594 | 567 | try depTokenizer("foo.o: foo.c", expect); |
| 595 | 568 | try depTokenizer( |
| ... | ... | @@ -608,9 +581,9 @@ test "prereq" { |
| 608 | 581 | |
| 609 | 582 | test "prereq continuation" { |
| 610 | 583 | const expect = |
| 611 | \\target = {foo.o} | |
| 612 | \\prereq = {foo.h} | |
| 613 | \\prereq = {bar.h} | |
| 584 | \\target = {"foo.o"} | |
| 585 | \\prereq = {"foo.h"} | |
| 586 | \\prereq = {"bar.h"} | |
| 614 | 587 | ; |
| 615 | 588 | try depTokenizer( |
| 616 | 589 | \\foo.o: foo.h\ |
| ... | ... | @@ -624,19 +597,19 @@ test "prereq continuation" { |
| 624 | 597 | |
| 625 | 598 | test "prereq continuation (CRLF)" { |
| 626 | 599 | const expect = |
| 627 | \\target = {foo.o} | |
| 628 | \\prereq = {foo.h} | |
| 629 | \\prereq = {bar.h} | |
| 600 | \\target = {"foo.o"} | |
| 601 | \\prereq = {"foo.h"} | |
| 602 | \\prereq = {"bar.h"} | |
| 630 | 603 | ; |
| 631 | 604 | try depTokenizer("foo.o: foo.h\\\r\nbar.h", expect); |
| 632 | 605 | } |
| 633 | 606 | |
| 634 | 607 | test "multiple prereqs" { |
| 635 | 608 | const expect = |
| 636 | \\target = {foo.o} | |
| 637 | \\prereq = {foo.c} | |
| 638 | \\prereq = {foo.h} | |
| 639 | \\prereq = {bar.h} | |
| 609 | \\target = {"foo.o"} | |
| 610 | \\prereq = {"foo.c"} | |
| 611 | \\prereq = {"foo.h"} | |
| 612 | \\prereq = {"bar.h"} | |
| 640 | 613 | ; |
| 641 | 614 | try depTokenizer("foo.o: foo.c foo.h bar.h", expect); |
| 642 | 615 | try depTokenizer( |
| ... | ... | @@ -680,19 +653,19 @@ test "multiple targets and prereqs" { |
| 680 | 653 | \\ one.h two.h \ |
| 681 | 654 | \\ three.h four.h |
| 682 | 655 | , |
| 683 | \\target = {foo.o} | |
| 684 | \\prereq = {foo.c} | |
| 685 | \\target = {bar.o} | |
| 686 | \\prereq = {bar.c} | |
| 687 | \\prereq = {a.h} | |
| 688 | \\prereq = {b.h} | |
| 689 | \\prereq = {c.h} | |
| 690 | \\target = {abc.o} | |
| 691 | \\prereq = {abc.c} | |
| 692 | \\prereq = {one.h} | |
| 693 | \\prereq = {two.h} | |
| 694 | \\prereq = {three.h} | |
| 695 | \\prereq = {four.h} | |
| 656 | \\target = {"foo.o"} | |
| 657 | \\prereq = {"foo.c"} | |
| 658 | \\target = {"bar.o"} | |
| 659 | \\prereq = {"bar.c"} | |
| 660 | \\prereq = {"a.h"} | |
| 661 | \\prereq = {"b.h"} | |
| 662 | \\prereq = {"c.h"} | |
| 663 | \\target = {"abc.o"} | |
| 664 | \\prereq = {"abc.c"} | |
| 665 | \\prereq = {"one.h"} | |
| 666 | \\prereq = {"two.h"} | |
| 667 | \\prereq = {"three.h"} | |
| 668 | \\prereq = {"four.h"} | |
| 696 | 669 | ); |
| 697 | 670 | try depTokenizer( |
| 698 | 671 | \\ascii.o: ascii.c |
| ... | ... | @@ -702,21 +675,21 @@ test "multiple targets and prereqs" { |
| 702 | 675 | \\ macho.c\ |
| 703 | 676 | \\ a.h b.h c.h |
| 704 | 677 | , |
| 705 | \\target = {ascii.o} | |
| 706 | \\prereq = {ascii.c} | |
| 707 | \\target = {base64.o} | |
| 708 | \\prereq = {base64.c} | |
| 709 | \\prereq = {stdio.h} | |
| 710 | \\target = {elf.o} | |
| 711 | \\prereq = {elf.c} | |
| 712 | \\prereq = {a.h} | |
| 713 | \\prereq = {b.h} | |
| 714 | \\prereq = {c.h} | |
| 715 | \\target = {macho.o} | |
| 716 | \\prereq = {macho.c} | |
| 717 | \\prereq = {a.h} | |
| 718 | \\prereq = {b.h} | |
| 719 | \\prereq = {c.h} | |
| 678 | \\target = {"ascii.o"} | |
| 679 | \\prereq = {"ascii.c"} | |
| 680 | \\target = {"base64.o"} | |
| 681 | \\prereq = {"base64.c"} | |
| 682 | \\prereq = {"stdio.h"} | |
| 683 | \\target = {"elf.o"} | |
| 684 | \\prereq = {"elf.c"} | |
| 685 | \\prereq = {"a.h"} | |
| 686 | \\prereq = {"b.h"} | |
| 687 | \\prereq = {"c.h"} | |
| 688 | \\target = {"macho.o"} | |
| 689 | \\prereq = {"macho.c"} | |
| 690 | \\prereq = {"a.h"} | |
| 691 | \\prereq = {"b.h"} | |
| 692 | \\prereq = {"c.h"} | |
| 720 | 693 | ); |
| 721 | 694 | try depTokenizer( |
| 722 | 695 | \\a$$scii.o: ascii.c |
| ... | ... | @@ -726,20 +699,20 @@ test "multiple targets and prereqs" { |
| 726 | 699 | \\ "macho!.c" \ |
| 727 | 700 | \\ a.h b.h c.h |
| 728 | 701 | , |
| 729 | \\target = {a$scii.o} | |
| 730 | \\prereq = {ascii.c} | |
| 731 | \\target = {\base64.o} | |
| 732 | \\prereq = {\base64.c} | |
| 733 | \\prereq = {s t#dio.h} | |
| 734 | \\target = {e\lf.o} | |
| 735 | \\prereq = {e\lf.c} | |
| 736 | \\prereq = {a.h$$} | |
| 737 | \\prereq = {$$b.h c.h$$} | |
| 738 | \\target = {macho.o} | |
| 739 | \\prereq = {macho!.c} | |
| 740 | \\prereq = {a.h} | |
| 741 | \\prereq = {b.h} | |
| 742 | \\prereq = {c.h} | |
| 702 | \\target = {"a$scii.o"} | |
| 703 | \\prereq = {"ascii.c"} | |
| 704 | \\target = {"\\base64.o"} | |
| 705 | \\prereq = {"\\base64.c"} | |
| 706 | \\prereq = {"s t#dio.h"} | |
| 707 | \\target = {"e\\lf.o"} | |
| 708 | \\prereq = {"e\\lf.c"} | |
| 709 | \\prereq = {"a.h$$"} | |
| 710 | \\prereq = {"$$b.h c.h$$"} | |
| 711 | \\target = {"macho.o"} | |
| 712 | \\prereq = {"macho!.c"} | |
| 713 | \\prereq = {"a.h"} | |
| 714 | \\prereq = {"b.h"} | |
| 715 | \\prereq = {"c.h"} | |
| 743 | 716 | ); |
| 744 | 717 | } |
| 745 | 718 | |
| ... | ... | @@ -750,12 +723,12 @@ test "windows quoted prereqs" { |
| 750 | 723 | \\ "C:\Program Files (x86)\Microsoft Visual Studio\foo1.h" \ |
| 751 | 724 | \\ "C:\Program Files (x86)\Microsoft Visual Studio\foo2.h" |
| 752 | 725 | , |
| 753 | \\target = {c:\foo.o} | |
| 754 | \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\foo.c} | |
| 755 | \\target = {c:\foo2.o} | |
| 756 | \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\foo2.c} | |
| 757 | \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\foo1.h} | |
| 758 | \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\foo2.h} | |
| 726 | \\target = {"c:\\foo.o"} | |
| 727 | \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\foo.c"} | |
| 728 | \\target = {"c:\\foo2.o"} | |
| 729 | \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\foo2.c"} | |
| 730 | \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\foo1.h"} | |
| 731 | \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\foo2.h"} | |
| 759 | 732 | ); |
| 760 | 733 | } |
| 761 | 734 | |
| ... | ... | @@ -779,23 +752,23 @@ test "windows mixed prereqs" { |
| 779 | 752 | \\ "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.21.27702\lib\x64\\..\..\include\vcruntime_string.h" \ |
| 780 | 753 | \\ "C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\corecrt_wstring.h" |
| 781 | 754 | , |
| 782 | \\target = {cimport.o} | |
| 783 | \\prereq = {C:\msys64\home\anon\project\zig\master\zig-cache\o\qhvhbUo7GU5iKyQ5mpA8TcQpncCYaQu0wwvr3ybiSTj_Dtqi1Nmcb70kfODJ2Qlg\cimport.h} | |
| 784 | \\prereq = {C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\stdio.h} | |
| 785 | \\prereq = {C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\corecrt.h} | |
| 786 | \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.21.27702\lib\x64\\..\..\include\vcruntime.h} | |
| 787 | \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.21.27702\lib\x64\\..\..\include\sal.h} | |
| 788 | \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.21.27702\lib\x64\\..\..\include\concurrencysal.h} | |
| 789 | \\prereq = {C:\msys64\opt\zig\lib\zig\include\vadefs.h} | |
| 790 | \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.21.27702\lib\x64\\..\..\include\vadefs.h} | |
| 791 | \\prereq = {C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\corecrt_wstdio.h} | |
| 792 | \\prereq = {C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\corecrt_stdio_config.h} | |
| 793 | \\prereq = {C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\string.h} | |
| 794 | \\prereq = {C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\corecrt_memory.h} | |
| 795 | \\prereq = {C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\corecrt_memcpy_s.h} | |
| 796 | \\prereq = {C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\errno.h} | |
| 797 | \\prereq = {C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.21.27702\lib\x64\\..\..\include\vcruntime_string.h} | |
| 798 | \\prereq = {C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17763.0\ucrt\corecrt_wstring.h} | |
| 755 | \\target = {"cimport.o"} | |
| 756 | \\prereq = {"C:\\msys64\\home\\anon\\project\\zig\\master\\zig-cache\\o\\qhvhbUo7GU5iKyQ5mpA8TcQpncCYaQu0wwvr3ybiSTj_Dtqi1Nmcb70kfODJ2Qlg\\cimport.h"} | |
| 757 | \\prereq = {"C:\\Program Files (x86)\\Windows Kits\\10\\\\Include\\10.0.17763.0\\ucrt\\stdio.h"} | |
| 758 | \\prereq = {"C:\\Program Files (x86)\\Windows Kits\\10\\\\Include\\10.0.17763.0\\ucrt\\corecrt.h"} | |
| 759 | \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC\\14.21.27702\\lib\\x64\\\\..\\..\\include\\vcruntime.h"} | |
| 760 | \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC\\14.21.27702\\lib\\x64\\\\..\\..\\include\\sal.h"} | |
| 761 | \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC\\14.21.27702\\lib\\x64\\\\..\\..\\include\\concurrencysal.h"} | |
| 762 | \\prereq = {"C:\\msys64\\opt\\zig\\lib\\zig\\include\\vadefs.h"} | |
| 763 | \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC\\14.21.27702\\lib\\x64\\\\..\\..\\include\\vadefs.h"} | |
| 764 | \\prereq = {"C:\\Program Files (x86)\\Windows Kits\\10\\\\Include\\10.0.17763.0\\ucrt\\corecrt_wstdio.h"} | |
| 765 | \\prereq = {"C:\\Program Files (x86)\\Windows Kits\\10\\\\Include\\10.0.17763.0\\ucrt\\corecrt_stdio_config.h"} | |
| 766 | \\prereq = {"C:\\Program Files (x86)\\Windows Kits\\10\\\\Include\\10.0.17763.0\\ucrt\\string.h"} | |
| 767 | \\prereq = {"C:\\Program Files (x86)\\Windows Kits\\10\\\\Include\\10.0.17763.0\\ucrt\\corecrt_memory.h"} | |
| 768 | \\prereq = {"C:\\Program Files (x86)\\Windows Kits\\10\\\\Include\\10.0.17763.0\\ucrt\\corecrt_memcpy_s.h"} | |
| 769 | \\prereq = {"C:\\Program Files (x86)\\Windows Kits\\10\\\\Include\\10.0.17763.0\\ucrt\\errno.h"} | |
| 770 | \\prereq = {"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC\\14.21.27702\\lib\\x64\\\\..\\..\\include\\vcruntime_string.h"} | |
| 771 | \\prereq = {"C:\\Program Files (x86)\\Windows Kits\\10\\\\Include\\10.0.17763.0\\ucrt\\corecrt_wstring.h"} | |
| 799 | 772 | ); |
| 800 | 773 | } |
| 801 | 774 | |
| ... | ... | @@ -810,14 +783,14 @@ test "windows funky targets" { |
| 810 | 783 | \\C:\Users\anon\$$foo.o: |
| 811 | 784 | \\C:\Users\anon\\\ \ \ \ \ foo.o: |
| 812 | 785 | , |
| 813 | \\target = {C:\Users\anon\foo.o} | |
| 814 | \\target = {C:\Users\anon\foo .o} | |
| 815 | \\target = {C:\Users\anon\foo#.o} | |
| 816 | \\target = {C:\Users\anon\foo$.o} | |
| 817 | \\target = {C:\Users\anon\ foo.o} | |
| 818 | \\target = {C:\Users\anon\#foo.o} | |
| 819 | \\target = {C:\Users\anon\$foo.o} | |
| 820 | \\target = {C:\Users\anon\ foo.o} | |
| 786 | \\target = {"C:\\Users\\anon\\foo.o"} | |
| 787 | \\target = {"C:\\Users\\anon\\foo .o"} | |
| 788 | \\target = {"C:\\Users\\anon\\foo#.o"} | |
| 789 | \\target = {"C:\\Users\\anon\\foo$.o"} | |
| 790 | \\target = {"C:\\Users\\anon\\ foo.o"} | |
| 791 | \\target = {"C:\\Users\\anon\\#foo.o"} | |
| 792 | \\target = {"C:\\Users\\anon\\$foo.o"} | |
| 793 | \\target = {"C:\\Users\\anon\\ foo.o"} | |
| 821 | 794 | ); |
| 822 | 795 | } |
| 823 | 796 | |
| ... | ... | @@ -837,13 +810,13 @@ test "windows funky prereqs" { |
| 837 | 810 | \\ somedir\\ \\ \c.c\ |
| 838 | 811 | \\ |
| 839 | 812 | , |
| 840 | \\target = {cimport.o} | |
| 841 | \\prereq = {trailingbackslash\} | |
| 842 | \\prereq = {C:\Users\John Smith\AppData\Local\zig\p\1220d14057af1a9d6dde4643293527bd5ee5099517d655251a066666a4320737ea7c\cimport.c} | |
| 843 | \\prereq = {somedir\ a.c} | |
| 844 | \\prereq = {somedir/ a.c} | |
| 845 | \\prereq = {somedir\ b.c} | |
| 846 | \\prereq = {somedir\ \ \c.c} | |
| 813 | \\target = {"cimport.o"} | |
| 814 | \\prereq = {"trailingbackslash\\"} | |
| 815 | \\prereq = {"C:\\Users\\John Smith\\AppData\\Local\\zig\\p\\1220d14057af1a9d6dde4643293527bd5ee5099517d655251a066666a4320737ea7c\\cimport.c"} | |
| 816 | \\prereq = {"somedir\\ a.c"} | |
| 817 | \\prereq = {"somedir/ a.c"} | |
| 818 | \\prereq = {"somedir\\ b.c"} | |
| 819 | \\prereq = {"somedir\\ \\ \\c.c"} | |
| 847 | 820 | ); |
| 848 | 821 | } |
| 849 | 822 | |
| ... | ... | @@ -852,171 +825,171 @@ test "windows drive and forward slashes" { |
| 852 | 825 | \\C:/msys64/what/zig-cache\tmp\48ac4d78dd531abd-cxa_thread_atexit.obj: \ |
| 853 | 826 | \\ 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} | |
| 856 | \\prereq = {C:/msys64/opt/zig3/lib/zig/libc/mingw/crt/cxa_thread_atexit.c} | |
| 828 | \\target = {"C:/msys64/what/zig-cache\\tmp\\48ac4d78dd531abd-cxa_thread_atexit.obj"} | |
| 829 | \\prereq = {"C:/msys64/opt/zig3/lib/zig/libc/mingw/crt/cxa_thread_atexit.c"} | |
| 857 | 830 | ); |
| 858 | 831 | } |
| 859 | 832 | |
| 860 | 833 | test "error incomplete escape - reverse_solidus" { |
| 861 | 834 | try depTokenizer("\\", |
| 862 | \\ERROR: illegal char '\' at position 0: incomplete escape | |
| 835 | \\ERROR: illegal char '\\' at position 0: incomplete_escape | |
| 863 | 836 | ); |
| 864 | 837 | try depTokenizer("\t\\", |
| 865 | \\ERROR: illegal char '\' at position 1: incomplete escape | |
| 838 | \\ERROR: illegal char '\\' at position 1: incomplete_escape | |
| 866 | 839 | ); |
| 867 | 840 | try depTokenizer("\n\\", |
| 868 | \\ERROR: illegal char '\' at position 1: incomplete escape | |
| 841 | \\ERROR: illegal char '\\' at position 1: incomplete_escape | |
| 869 | 842 | ); |
| 870 | 843 | try depTokenizer("\r\\", |
| 871 | \\ERROR: illegal char '\' at position 1: incomplete escape | |
| 844 | \\ERROR: illegal char '\\' at position 1: incomplete_escape | |
| 872 | 845 | ); |
| 873 | 846 | try depTokenizer("\r\n\\", |
| 874 | \\ERROR: illegal char '\' at position 2: incomplete escape | |
| 847 | \\ERROR: illegal char '\\' at position 2: incomplete_escape | |
| 875 | 848 | ); |
| 876 | 849 | try depTokenizer(" \\", |
| 877 | \\ERROR: illegal char '\' at position 1: incomplete escape | |
| 850 | \\ERROR: illegal char '\\' at position 1: incomplete_escape | |
| 878 | 851 | ); |
| 879 | 852 | } |
| 880 | 853 | |
| 881 | 854 | test "error incomplete escape - dollar_sign" { |
| 882 | 855 | try depTokenizer("$", |
| 883 | \\ERROR: illegal char '$' at position 0: incomplete escape | |
| 856 | \\ERROR: illegal char '$' at position 0: incomplete_escape | |
| 884 | 857 | ); |
| 885 | 858 | try depTokenizer("\t$", |
| 886 | \\ERROR: illegal char '$' at position 1: incomplete escape | |
| 859 | \\ERROR: illegal char '$' at position 1: incomplete_escape | |
| 887 | 860 | ); |
| 888 | 861 | try depTokenizer("\n$", |
| 889 | \\ERROR: illegal char '$' at position 1: incomplete escape | |
| 862 | \\ERROR: illegal char '$' at position 1: incomplete_escape | |
| 890 | 863 | ); |
| 891 | 864 | try depTokenizer("\r$", |
| 892 | \\ERROR: illegal char '$' at position 1: incomplete escape | |
| 865 | \\ERROR: illegal char '$' at position 1: incomplete_escape | |
| 893 | 866 | ); |
| 894 | 867 | try depTokenizer("\r\n$", |
| 895 | \\ERROR: illegal char '$' at position 2: incomplete escape | |
| 868 | \\ERROR: illegal char '$' at position 2: incomplete_escape | |
| 896 | 869 | ); |
| 897 | 870 | try depTokenizer(" $", |
| 898 | \\ERROR: illegal char '$' at position 1: incomplete escape | |
| 871 | \\ERROR: illegal char '$' at position 1: incomplete_escape | |
| 899 | 872 | ); |
| 900 | 873 | } |
| 901 | 874 | |
| 902 | 875 | test "error incomplete target" { |
| 903 | 876 | try depTokenizer("foo.o", |
| 904 | \\ERROR: incomplete target 'foo.o' at position 0 | |
| 877 | \\ERROR: incomplete_target "foo.o" at position 0 | |
| 905 | 878 | ); |
| 906 | 879 | try depTokenizer("\tfoo.o", |
| 907 | \\ERROR: incomplete target 'foo.o' at position 1 | |
| 880 | \\ERROR: incomplete_target "foo.o" at position 1 | |
| 908 | 881 | ); |
| 909 | 882 | try depTokenizer("\nfoo.o", |
| 910 | \\ERROR: incomplete target 'foo.o' at position 1 | |
| 883 | \\ERROR: incomplete_target "foo.o" at position 1 | |
| 911 | 884 | ); |
| 912 | 885 | try depTokenizer("\rfoo.o", |
| 913 | \\ERROR: incomplete target 'foo.o' at position 1 | |
| 886 | \\ERROR: incomplete_target "foo.o" at position 1 | |
| 914 | 887 | ); |
| 915 | 888 | try depTokenizer("\r\nfoo.o", |
| 916 | \\ERROR: incomplete target 'foo.o' at position 2 | |
| 889 | \\ERROR: incomplete_target "foo.o" at position 2 | |
| 917 | 890 | ); |
| 918 | 891 | try depTokenizer(" foo.o", |
| 919 | \\ERROR: incomplete target 'foo.o' at position 1 | |
| 892 | \\ERROR: incomplete_target "foo.o" at position 1 | |
| 920 | 893 | ); |
| 921 | 894 | |
| 922 | 895 | try depTokenizer("\\ foo.o", |
| 923 | \\ERROR: incomplete target ' foo.o' at position 0 | |
| 896 | \\ERROR: incomplete_target "\\ foo.o" at position 0 | |
| 924 | 897 | ); |
| 925 | 898 | try depTokenizer("\\#foo.o", |
| 926 | \\ERROR: incomplete target '#foo.o' at position 0 | |
| 899 | \\ERROR: incomplete_target "\\#foo.o" at position 0 | |
| 927 | 900 | ); |
| 928 | 901 | try depTokenizer("\\\\foo.o", |
| 929 | \\ERROR: incomplete target '\foo.o' at position 0 | |
| 902 | \\ERROR: incomplete_target "\\\\foo.o" at position 0 | |
| 930 | 903 | ); |
| 931 | 904 | try depTokenizer("$$foo.o", |
| 932 | \\ERROR: incomplete target '$foo.o' at position 0 | |
| 905 | \\ERROR: incomplete_target "$$foo.o" at position 0 | |
| 933 | 906 | ); |
| 934 | 907 | } |
| 935 | 908 | |
| 936 | 909 | test "error illegal char at position - bad target escape" { |
| 937 | 910 | try depTokenizer("\\\t", |
| 938 | \\ERROR: illegal char \x09 at position 1: bad target escape | |
| 911 | \\ERROR: illegal char '\t' at position 1: bad_target_escape | |
| 939 | 912 | ); |
| 940 | 913 | try depTokenizer("\\\n", |
| 941 | \\ERROR: illegal char \x0A at position 1: bad target escape | |
| 914 | \\ERROR: illegal char '\n' at position 1: bad_target_escape | |
| 942 | 915 | ); |
| 943 | 916 | try depTokenizer("\\\r", |
| 944 | \\ERROR: illegal char \x0D at position 1: bad target escape | |
| 917 | \\ERROR: illegal char '\r' at position 1: bad_target_escape | |
| 945 | 918 | ); |
| 946 | 919 | try depTokenizer("\\\r\n", |
| 947 | \\ERROR: illegal char \x0D at position 1: bad target escape | |
| 920 | \\ERROR: illegal char '\r' at position 1: bad_target_escape | |
| 948 | 921 | ); |
| 949 | 922 | } |
| 950 | 923 | |
| 951 | 924 | test "error illegal char at position - expecting dollar_sign" { |
| 952 | 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 | 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 | 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 | 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 | } |
| 965 | 938 | |
| 966 | 939 | test "error illegal char at position - invalid target" { |
| 967 | 940 | try depTokenizer("foo\n.o", |
| 968 | \\ERROR: illegal char \x0A at position 3: invalid target | |
| 941 | \\ERROR: illegal char '\n' at position 3: invalid_target | |
| 969 | 942 | ); |
| 970 | 943 | try depTokenizer("foo\r.o", |
| 971 | \\ERROR: illegal char \x0D at position 3: invalid target | |
| 944 | \\ERROR: illegal char '\r' at position 3: invalid_target | |
| 972 | 945 | ); |
| 973 | 946 | try depTokenizer("foo\r\n.o", |
| 974 | \\ERROR: illegal char \x0D at position 3: invalid target | |
| 947 | \\ERROR: illegal char '\r' at position 3: invalid_target | |
| 975 | 948 | ); |
| 976 | 949 | } |
| 977 | 950 | |
| 978 | 951 | test "error target - continuation expecting end-of-line" { |
| 979 | 952 | try depTokenizer("foo.o: \\\t", |
| 980 | \\target = {foo.o} | |
| 981 | \\ERROR: illegal char \x09 at position 8: continuation expecting end-of-line | |
| 953 | \\target = {"foo.o"} | |
| 954 | \\ERROR: illegal char '\t' at position 8: continuation_eol | |
| 982 | 955 | ); |
| 983 | 956 | try depTokenizer("foo.o: \\ ", |
| 984 | \\target = {foo.o} | |
| 985 | \\ERROR: illegal char ' ' at position 8: continuation expecting end-of-line | |
| 957 | \\target = {"foo.o"} | |
| 958 | \\ERROR: illegal char ' ' at position 8: continuation_eol | |
| 986 | 959 | ); |
| 987 | 960 | try depTokenizer("foo.o: \\x", |
| 988 | \\target = {foo.o} | |
| 989 | \\ERROR: illegal char 'x' at position 8: continuation expecting end-of-line | |
| 961 | \\target = {"foo.o"} | |
| 962 | \\ERROR: illegal char 'x' at position 8: continuation_eol | |
| 990 | 963 | ); |
| 991 | 964 | try depTokenizer("foo.o: \\\x0dx", |
| 992 | \\target = {foo.o} | |
| 993 | \\ERROR: illegal char 'x' at position 9: continuation expecting end-of-line | |
| 965 | \\target = {"foo.o"} | |
| 966 | \\ERROR: illegal char 'x' at position 9: continuation_eol | |
| 994 | 967 | ); |
| 995 | 968 | } |
| 996 | 969 | |
| 997 | 970 | test "error prereq - continuation expecting end-of-line" { |
| 998 | 971 | try depTokenizer("foo.o: foo.h\\\x0dx", |
| 999 | \\target = {foo.o} | |
| 1000 | \\ERROR: illegal char 'x' at position 14: continuation expecting end-of-line | |
| 972 | \\target = {"foo.o"} | |
| 973 | \\ERROR: illegal char 'x' at position 14: continuation_eol | |
| 1001 | 974 | ); |
| 1002 | 975 | } |
| 1003 | 976 | |
| 1004 | 977 | test "error illegal char at position - expecting colon" { |
| 1005 | 978 | try depTokenizer("foo\t.o:", |
| 1006 | \\target = {foo} | |
| 1007 | \\ERROR: illegal char '.' at position 4: expecting ':' | |
| 979 | \\target = {"foo"} | |
| 980 | \\ERROR: illegal char '.' at position 4: expected_colon | |
| 1008 | 981 | ); |
| 1009 | 982 | try depTokenizer("foo .o:", |
| 1010 | \\target = {foo} | |
| 1011 | \\ERROR: illegal char '.' at position 4: expecting ':' | |
| 983 | \\target = {"foo"} | |
| 984 | \\ERROR: illegal char '.' at position 4: expected_colon | |
| 1012 | 985 | ); |
| 1013 | 986 | try depTokenizer("foo \n.o:", |
| 1014 | \\target = {foo} | |
| 1015 | \\ERROR: illegal char \x0A at position 4: expecting ':' | |
| 987 | \\target = {"foo"} | |
| 988 | \\ERROR: illegal char '\n' at position 4: expected_colon | |
| 1016 | 989 | ); |
| 1017 | 990 | try depTokenizer("foo.o\t\n:", |
| 1018 | \\target = {foo.o} | |
| 1019 | \\ERROR: illegal char \x0A at position 6: expecting ':' | |
| 991 | \\target = {"foo.o"} | |
| 992 | \\ERROR: illegal char '\n' at position 6: expected_colon | |
| 1020 | 993 | ); |
| 1021 | 994 | } |
| 1022 | 995 | |
| ... | ... | @@ -1028,70 +1001,35 @@ fn depTokenizer(input: []const u8, expect: []const u8) !void { |
| 1028 | 1001 | |
| 1029 | 1002 | var it: Tokenizer = .{ .bytes = input }; |
| 1030 | 1003 | var buffer: std.ArrayList(u8) = .empty; |
| 1031 | var resolve_buf: std.ArrayList(u8) = .empty; | |
| 1004 | var aux: std.ArrayList(u8) = .empty; | |
| 1032 | 1005 | var i: usize = 0; |
| 1033 | 1006 | while (it.next()) |token| { |
| 1034 | 1007 | if (i != 0) try buffer.appendSlice(arena, "\n"); |
| 1035 | 1008 | switch (token) { |
| 1036 | 1009 | .target, .prereq => |bytes| { |
| 1037 | try buffer.appendSlice(arena, @tagName(token)); | |
| 1038 | try buffer.appendSlice(arena, " = {"); | |
| 1039 | for (bytes) |b| { | |
| 1040 | try buffer.append(arena, printable_char_tab[b]); | |
| 1041 | } | |
| 1042 | try buffer.appendSlice(arena, "}"); | |
| 1010 | try buffer.print(arena, "{t} = {{{q}}}", .{ token, bytes }); | |
| 1043 | 1011 | }, |
| 1044 | 1012 | .target_must_resolve => { |
| 1045 | 1013 | try buffer.appendSlice(arena, "target = {"); |
| 1046 | try token.resolve(arena, &resolve_buf); | |
| 1047 | for (resolve_buf.items) |b| { | |
| 1048 | try buffer.append(arena, printable_char_tab[b]); | |
| 1049 | } | |
| 1050 | resolve_buf.items.len = 0; | |
| 1014 | aux.clearRetainingCapacity(); | |
| 1015 | try token.resolve(arena, &aux); | |
| 1016 | try buffer.print(arena, "{q}", .{aux.items}); | |
| 1051 | 1017 | try buffer.appendSlice(arena, "}"); |
| 1052 | 1018 | }, |
| 1053 | 1019 | .prereq_must_resolve => { |
| 1054 | 1020 | try buffer.appendSlice(arena, "prereq = {"); |
| 1055 | try token.resolve(arena, &resolve_buf); | |
| 1056 | for (resolve_buf.items) |b| { | |
| 1057 | try buffer.append(arena, printable_char_tab[b]); | |
| 1058 | } | |
| 1059 | resolve_buf.items.len = 0; | |
| 1021 | aux.clearRetainingCapacity(); | |
| 1022 | try token.resolve(arena, &aux); | |
| 1023 | try buffer.print(arena, "{q}", .{aux.items}); | |
| 1060 | 1024 | try buffer.appendSlice(arena, "}"); |
| 1061 | 1025 | }, |
| 1062 | 1026 | else => { |
| 1063 | try buffer.appendSlice(arena, "ERROR: "); | |
| 1064 | try token.printError(arena, &buffer); | |
| 1027 | try buffer.print(arena, "ERROR: {f}", .{token}); | |
| 1065 | 1028 | break; |
| 1066 | 1029 | }, |
| 1067 | 1030 | } |
| 1068 | 1031 | i += 1; |
| 1069 | 1032 | } |
| 1070 | 1033 | |
| 1071 | if (std.mem.eql(u8, expect, buffer.items)) { | |
| 1072 | try testing.expect(true); | |
| 1073 | return; | |
| 1074 | } | |
| 1075 | ||
| 1076 | 1034 | try testing.expectEqualStrings(expect, buffer.items); |
| 1077 | 1035 | } |
| 1078 | ||
| 1079 | fn 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 | ||
| 1083 | fn 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 | |
| 1092 | const 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 | 1873 | pkg: Package.OptionalIndex, |
| 1874 | 1874 | |
| 1875 | 1875 | pub const Flags = packed struct(u32) { |
| 1876 | mode: Mode, | |
| 1876 | is_directory: bool, | |
| 1877 | metadata_only: bool, | |
| 1877 | 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 | }; |
| 1883 | 1882 | |
| 1884 | 1883 | pub 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 | 34 | .cwd_relative => .cwd, |
| 35 | 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 | 40 | .sub = switch (src.lazy_path) { |
| 40 | 41 | .src_path => |sp| try wc.addString(sp.sub_path), |
lib/std/Io/Reader.zig+1| ... | ... | @@ -397,6 +397,7 @@ pub fn appendRemainingAligned( |
| 397 | 397 | pub const UnlimitedAllocError = Allocator.Error || ShortError; |
| 398 | 398 | |
| 399 | 399 | pub fn appendRemainingUnlimited(r: *Reader, gpa: Allocator, list: *ArrayList(u8)) UnlimitedAllocError!void { |
| 400 | list.pointer_stability.assertUnlocked(); | |
| 400 | 401 | var a: std.Io.Writer.Allocating = .initOwnedSlice(gpa, list.allocatedSlice()); |
| 401 | 402 | a.writer.end = list.items.len; |
| 402 | 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 | 776 | /// |
| 777 | 777 | /// Asserts what the capacity is equal to the length. |
| 778 | 778 | /// Never invalidates element pointers. |
| 779 | /// | |
| 780 | /// See also: | |
| 781 | /// * `shrinkToLen` | |
| 779 | 782 | pub fn toOwnedSliceAssert(self: *Self) Slice { |
| 780 | 783 | assert(self.items.len == self.capacity); |
| 781 | 784 | const items = self.items; |
lib/std/testing.zig+3| ... | ... | @@ -592,6 +592,9 @@ pub const TmpDir = struct { |
| 592 | 592 | const random_bytes_count = 12; |
| 593 | 593 | const sub_path_len = std.base64.url_safe.Encoder.calcSize(random_bytes_count); |
| 594 | 594 | |
| 595 | /// Deprecated. | |
| 596 | pub const parent_dir_path: []const u8 = ".zig-cache" ++ std.fs.path.sep_str ++ "tmp"; | |
| 597 | ||
| 595 | 598 | pub fn cleanup(self: *TmpDir) void { |
| 596 | 599 | self.dir.close(io); |
| 597 | 600 | self.parent_dir.deleteTree(io, &self.sub_path) catch {}; |
lib/std/zig.zig+6-6| ... | ... | @@ -1838,12 +1838,12 @@ pub fn buildExeSubprocess( |
| 1838 | 1838 | var it = mem.splitScalar(u8, body, 0); |
| 1839 | 1839 | while (it.next()) |prefixed_path| { |
| 1840 | 1840 | const prefix: Server.Message.PathPrefix = @fromBackingInt(@intCast(prefixed_path[0] - 1)); |
| 1841 | const sub_path = try gpa.dupe(u8, prefixed_path[1..]); | |
| 1842 | var keep = false; | |
| 1843 | defer if (!keep) gpa.free(sub_path); | |
| 1844 | keep = man.addPrefixedPathPost(.{ | |
| 1845 | .prefix = @backingInt(prefix), | |
| 1846 | .sub_path = sub_path, | |
| 1841 | const sub_path = prefixed_path[1..]; | |
| 1842 | man.addDiscoveredPath(.{ | |
| 1843 | .discovered_path = .{ .prefixed = .{ | |
| 1844 | .prefix = @intCast(@backingInt(prefix)), | |
| 1845 | .sub_path = sub_path, | |
| 1846 | } }, | |
| 1847 | 1847 | }) catch |err| switch (err) { |
| 1848 | 1848 | error.Canceled, error.OutOfMemory => |e| return e, |
| 1849 | 1849 | else => |e| { |
src/Compilation.zig+148-132| ... | ... | @@ -701,39 +701,12 @@ pub const Path = struct { |
| 701 | 701 | }; |
| 702 | 702 | } |
| 703 | 703 | |
| 704 | pub fn addToCacheManifestPostHit(p: Path, man: *Cache.Manifest, dirs: *const std.zig.Directories) !void { | |
| 705 | comptime assert(0 == @backingInt(std.zig.Server.Message.PathPrefix.cwd)); | |
| 706 | comptime assert(1 == @backingInt(std.zig.Server.Message.PathPrefix.zig_lib)); | |
| 707 | comptime assert(2 == @backingInt(std.zig.Server.Message.PathPrefix.local_cache)); | |
| 708 | comptime assert(3 == @backingInt(std.zig.Server.Message.PathPrefix.global_cache)); | |
| 709 | comptime assert(4 == @backingInt(std.zig.Server.Message.PathPrefix.build_root)); | |
| 710 | comptime assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == 5); | |
| 711 | const gpa = man.cache.gpa; | |
| 712 | const prefixed_path: Cache.PrefixedPath = .{ | |
| 713 | .prefix = switch (p.root) { | |
| 714 | .none => { | |
| 715 | const path = try p.toAbsolute(dirs, gpa); | |
| 716 | defer gpa.free(path); | |
| 717 | return man.addFilePost(path); | |
| 718 | }, | |
| 719 | .zig_lib => 1, | |
| 720 | .local_cache => 2, | |
| 721 | .global_cache => 3, | |
| 722 | .build_root => 4, | |
| 723 | }, | |
| 724 | .sub_path = try gpa.dupe(u8, p.sub_path), | |
| 725 | }; | |
| 726 | var keep = false; | |
| 727 | defer if (!keep) gpa.free(prefixed_path.sub_path); | |
| 728 | keep = try man.addPrefixedPathPost(prefixed_path); | |
| 729 | } | |
| 730 | ||
| 731 | pub fn addToCacheManifestPostHitContents( | |
| 704 | pub fn addToCacheManifestAsDiscovered( | |
| 732 | 705 | p: Path, |
| 733 | 706 | man: *Cache.Manifest, |
| 734 | 707 | dirs: *const std.zig.Directories, |
| 735 | bytes: []const u8, | |
| 736 | stat: Cache.File.Stat, | |
| 708 | contents: ?[]const u8, | |
| 709 | stat: ?Cache.Manifest.Stat, | |
| 737 | 710 | ) !void { |
| 738 | 711 | comptime assert(0 == @backingInt(std.zig.Server.Message.PathPrefix.cwd)); |
| 739 | 712 | comptime assert(1 == @backingInt(std.zig.Server.Message.PathPrefix.zig_lib)); |
| ... | ... | @@ -741,24 +714,29 @@ pub const Path = struct { |
| 741 | 714 | comptime assert(3 == @backingInt(std.zig.Server.Message.PathPrefix.global_cache)); |
| 742 | 715 | comptime assert(4 == @backingInt(std.zig.Server.Message.PathPrefix.build_root)); |
| 743 | 716 | comptime assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == 5); |
| 744 | const gpa = man.cache.gpa; | |
| 745 | const prefixed_path: Cache.PrefixedPath = .{ | |
| 746 | .prefix = switch (p.root) { | |
| 747 | .none => { | |
| 748 | const path = try p.toAbsolute(dirs, gpa); | |
| 749 | defer gpa.free(path); | |
| 750 | return man.addFilePostContents(path, bytes, stat); | |
| 717 | try man.addDiscoveredPath(.{ | |
| 718 | .discovered_path = .{ .prefixed = .{ | |
| 719 | .prefix = switch (p.root) { | |
| 720 | .none => { | |
| 721 | const gpa = man.cache.gpa; | |
| 722 | const path = try p.toAbsolute(dirs, gpa); | |
| 723 | defer gpa.free(path); | |
| 724 | return man.addDiscoveredPath(.{ | |
| 725 | .discovered_path = .{ .unresolved = .initCwd(path) }, | |
| 726 | .contents = contents, | |
| 727 | .stat = stat, | |
| 728 | }); | |
| 729 | }, | |
| 730 | .zig_lib => 1, | |
| 731 | .local_cache => 2, | |
| 732 | .global_cache => 3, | |
| 733 | .build_root => 4, | |
| 751 | 734 | }, |
| 752 | .zig_lib => 1, | |
| 753 | .local_cache => 2, | |
| 754 | .global_cache => 3, | |
| 755 | .build_root => 4, | |
| 756 | }, | |
| 757 | .sub_path = try gpa.dupe(u8, p.sub_path), | |
| 758 | }; | |
| 759 | var keep = false; | |
| 760 | defer if (!keep) gpa.free(prefixed_path.sub_path); | |
| 761 | keep = try man.addPrefixedPathPostContents(prefixed_path, bytes, stat); | |
| 735 | .sub_path = p.sub_path, | |
| 736 | } }, | |
| 737 | .contents = contents, | |
| 738 | .stat = stat, | |
| 739 | }); | |
| 762 | 740 | } |
| 763 | 741 | }; |
| 764 | 742 | |
| ... | ... | @@ -1341,19 +1319,19 @@ pub const cache_helpers = struct { |
| 1341 | 1319 | } |
| 1342 | 1320 | } |
| 1343 | 1321 | |
| 1344 | pub fn hashCSource(self: *Cache.Manifest, c_source: CSourceFile) !void { | |
| 1345 | _ = try self.addFilePath(.initCwd(c_source.src_path), null); | |
| 1322 | pub fn hashCSource(man: *Cache.Manifest, c_source: CSourceFile) !void { | |
| 1323 | _ = try man.addInputPath(.initCwd(c_source.src_path), .{}); | |
| 1346 | 1324 | // Hash the extra flags, with special care to call addFile for file parameters. |
| 1347 | 1325 | // TODO this logic can likely be improved by utilizing clang_options_data.zig. |
| 1348 | 1326 | const file_args = [_][]const u8{"-include"}; |
| 1349 | 1327 | var arg_i: usize = 0; |
| 1350 | 1328 | while (arg_i < c_source.extra_flags.len) : (arg_i += 1) { |
| 1351 | 1329 | const arg = c_source.extra_flags[arg_i]; |
| 1352 | self.hash.addBytes(arg); | |
| 1330 | man.hash.addBytes(arg); | |
| 1353 | 1331 | for (file_args) |file_arg| { |
| 1354 | 1332 | if (mem.eql(u8, file_arg, arg) and arg_i + 1 < c_source.extra_flags.len) { |
| 1355 | 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 | 2813 | man.want_shared_lock = false; |
| 2836 | 2814 | } |
| 2837 | 2815 | |
| 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 | 2818 | error.Canceled, error.OutOfMemory => |e| return e, |
| 2840 | error.CacheCheckFailed => switch (man.diagnostic) { | |
| 2841 | .none => unreachable, | |
| 2842 | .manifest_create, .manifest_read, .manifest_lock => |e| return comp.setMiscFailure( | |
| 2843 | .check_whole_cache, | |
| 2844 | "failed to check cache: {t} {t}", | |
| 2845 | .{ man.diagnostic, e }, | |
| 2846 | ), | |
| 2847 | .file_open, .file_stat, .file_read, .file_hash => |op| { | |
| 2848 | const pp = man.files.keys()[op.file_index].prefixed_path; | |
| 2849 | const prefix = man.cache.prefixes()[pp.prefix]; | |
| 2850 | return comp.setMiscFailure(.check_whole_cache, "failed to check cache: {f}{s} {t} {t}", .{ | |
| 2851 | prefix, pp.sub_path, man.diagnostic, op.err, | |
| 2852 | }); | |
| 2853 | }, | |
| 2819 | error.CacheCheckFailed => { | |
| 2820 | return comp.setMiscFailure(.check_whole_cache, "checking cache failed: {f}", .{diag.fmt(&man)}); | |
| 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 | 2825 | // In this case the cache hit contains the full set of file system inputs. Nice! |
| 2863 | 2826 | if (comp.file_system_inputs) |buf| try man.populateFileSystemInputs(buf); |
| 2864 | 2827 | if (comp.parent_whole_cache) |pwc| { |
| 2865 | 2828 | try pwc.mutex.lock(io); |
| 2866 | 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 | } |
| 2869 | 2832 | |
| 2870 | 2833 | comp.last_update_was_cache_hit = true; |
| 2871 | log.debug("CacheMode.whole cache hit for {s}", .{comp.root_name}); | |
| 2872 | const bin_digest = man.finalBin(); | |
| 2834 | const bin_digest = man.hitDigest(); | |
| 2873 | 2835 | |
| 2874 | 2836 | comp.digest = bin_digest; |
| 2875 | 2837 | |
| ... | ... | @@ -2877,10 +2839,9 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE |
| 2877 | 2839 | whole.lock = man.toOwnedLock(); |
| 2878 | 2840 | return; |
| 2879 | 2841 | } |
| 2880 | log.debug("CacheMode.whole cache miss for {s}", .{comp.root_name}); | |
| 2881 | 2842 | |
| 2882 | 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 | 2845 | man.want_shared_lock = true; |
| 2885 | 2846 | } |
| 2886 | 2847 | |
| ... | ... | @@ -3036,7 +2997,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE |
| 3036 | 2997 | } |
| 3037 | 2998 | |
| 3038 | 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 | 3001 | zcu.intern_pool.dump(); |
| 3041 | 3002 | } |
| 3042 | 3003 | |
| ... | ... | @@ -3048,11 +3009,8 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE |
| 3048 | 3009 | |
| 3049 | 3010 | if (comp.link_depfile) |depfile_path| if (comp.bin_file) |lf| { |
| 3050 | 3011 | assert(comp.file_system_inputs != null); |
| 3051 | comp.createDepFile(depfile_path, lf.emit) catch |err| comp.setMiscFailure( | |
| 3052 | .link_depfile, | |
| 3053 | "unable to write linker dependency file: {t}", | |
| 3054 | .{err}, | |
| 3055 | ); | |
| 3012 | comp.createDepFile(depfile_path, lf.emit) catch |err| | |
| 3013 | comp.setMiscFailure(.link_depfile, "writing linker dependency file failed: {t}", .{err}); | |
| 3056 | 3014 | }; |
| 3057 | 3015 | |
| 3058 | 3016 | if (anyErrors(comp)) { |
| ... | ... | @@ -3075,10 +3033,10 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE |
| 3075 | 3033 | if (comp.parent_whole_cache) |pwc| { |
| 3076 | 3034 | try pwc.mutex.lock(io); |
| 3077 | 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 | } |
| 3080 | 3038 | |
| 3081 | const bin_digest = man.finalBin(); | |
| 3039 | const bin_digest = man.missDigest(); | |
| 3082 | 3040 | const hex_digest = Cache.binToHex(bin_digest); |
| 3083 | 3041 | |
| 3084 | 3042 | // 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 | 3126 | } |
| 3169 | 3127 | |
| 3170 | 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}); | |
| 3172 | 3130 | |
| 3173 | 3131 | assert(whole.lock == null); |
| 3174 | 3132 | whole.lock = man.toOwnedLock(); |
| ... | ... | @@ -3410,7 +3368,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes |
| 3410 | 3368 | try link.hashInputs(man, comp.link_inputs); |
| 3411 | 3369 | |
| 3412 | 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 | 3372 | man.hash.addOptional(c_object.src.ext); |
| 3415 | 3373 | man.hash.addListOfBytes(c_object.src.extra_flags); |
| 3416 | 3374 | } |
| ... | ... | @@ -3418,11 +3376,11 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes |
| 3418 | 3376 | for (comp.win32_resources.items) |win32_resource| { |
| 3419 | 3377 | switch (win32_resource.src) { |
| 3420 | 3378 | .rc => |rc_src| { |
| 3421 | _ = try man.addFilePath(.initCwd(rc_src.src_path), null); | |
| 3379 | _ = try man.addInputPath(.initCwd(rc_src.src_path), .{}); | |
| 3422 | 3380 | man.hash.addListOfBytes(rc_src.extra_flags); |
| 3423 | 3381 | }, |
| 3424 | 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 | 3412 | |
| 3455 | 3413 | const opts = comp.cache_use.whole.lf_open_opts; |
| 3456 | 3414 | |
| 3457 | try man.addOptionalFilePath(opts.linker_script); | |
| 3458 | try man.addOptionalFilePath(opts.version_script); | |
| 3415 | try man.addInputPathOptional(opts.linker_script, .{}); | |
| 3416 | try man.addInputPathOptional(opts.version_script, .{}); | |
| 3459 | 3417 | man.hash.add(opts.allow_undefined_version); |
| 3460 | 3418 | man.hash.addOptional(opts.enable_new_dtags); |
| 3461 | 3419 | |
| ... | ... | @@ -3514,7 +3472,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes |
| 3514 | 3472 | |
| 3515 | 3473 | // Mach-O specific stuff |
| 3516 | 3474 | try link.File.MachO.hashAddFrameworks(man, opts.frameworks); |
| 3517 | try man.addOptionalFilePath(opts.entitlements); | |
| 3475 | try man.addInputPathOptional(opts.entitlements, .{}); | |
| 3518 | 3476 | man.hash.addOptional(opts.pagezero_size); |
| 3519 | 3477 | man.hash.addOptional(opts.headerpad_size); |
| 3520 | 3478 | man.hash.add(opts.headerpad_max_install_names); |
| ... | ... | @@ -5123,9 +5081,13 @@ pub fn translateC( |
| 5123 | 5081 | try @import("main.zig").translateC(gpa, arena, io, argv.items, environ_map, prog_node, comp.thread_limit, &stdout); |
| 5124 | 5082 | |
| 5125 | 5083 | if (out_dep_path) |dep_file_path| add_deps: { |
| 5084 | var diagnostic: Cache.Manifest.AddDiscoveredDepFileDiagnostic = undefined; | |
| 5126 | 5085 | const dep_basename = fs.path.basename(dep_file_path); |
| 5127 | 5086 | // 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 | 5091 | error.FileNotFound => break :add_deps, |
| 5130 | 5092 | else => |e| return e, |
| 5131 | 5093 | }; |
| ... | ... | @@ -5134,14 +5096,17 @@ pub fn translateC( |
| 5134 | 5096 | .whole => |whole| if (whole.cache_manifest) |whole_cache_manifest| { |
| 5135 | 5097 | try whole.cache_manifest_mutex.lock(io); |
| 5136 | 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 | 5104 | .incremental, .none => {}, |
| 5140 | 5105 | } |
| 5141 | 5106 | |
| 5142 | 5107 | // Just to save disk space, we delete the file because it is never needed again. |
| 5143 | 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 | } |
| 5147 | 5112 | |
| ... | ... | @@ -5149,9 +5114,9 @@ pub fn translateC( |
| 5149 | 5114 | var reader: std.Io.Reader = .fixed(stdout); |
| 5150 | 5115 | const MessageHeader = std.zig.Server.Message.Header; |
| 5151 | 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 | 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 | 5120 | switch (header.tag) { |
| 5156 | 5121 | .error_bundle => { |
| 5157 | 5122 | const error_bundle = try std.zig.Server.allocErrorBundle(gpa, body); |
| ... | ... | @@ -5161,11 +5126,11 @@ pub fn translateC( |
| 5161 | 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 | } |
| 5167 | 5132 | |
| 5168 | const bin_digest = man.finalBin(); | |
| 5133 | const bin_digest = man.missDigest(); | |
| 5169 | 5134 | const hex_digest = Cache.binToHex(bin_digest); |
| 5170 | 5135 | const o_sub_path = "o" ++ fs.path.sep_str ++ hex_digest; |
| 5171 | 5136 | |
| ... | ... | @@ -5556,7 +5521,18 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr |
| 5556 | 5521 | const target = comp.getTarget(); |
| 5557 | 5522 | assert(target.ofmt != .c); |
| 5558 | 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 | 5536 | var argv: std.array_list.Managed([]const u8) = .init(gpa); |
| 5561 | 5537 | defer argv.deinit(); |
| 5562 | 5538 | |
| ... | ... | @@ -5683,11 +5659,11 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr |
| 5683 | 5659 | // Just to save disk space, we delete the files that are never needed again. |
| 5684 | 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 | 5661 | 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 | 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 | 5665 | 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 | 5668 | if (std.process.can_spawn) { |
| 5693 | 5669 | if (comp.clang_passthrough_mode) { |
| ... | ... | @@ -5776,8 +5752,10 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr |
| 5776 | 5752 | const dep_basename = fs.path.basename(dep_file_path); |
| 5777 | 5753 | |
| 5778 | 5754 | if (comp.file_system_inputs != null) { |
| 5755 | // TODO instead of this rely on passing the manifest contents directly | |
| 5756 | ||
| 5779 | 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 | 5759 | defer gpa.free(dep_file_contents); |
| 5782 | 5760 | |
| 5783 | 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 | 5770 | try token.resolve(gpa, &str_buf); |
| 5793 | 5771 | break :p try .fromUnresolved(arena, comp.dirs, &.{str_buf.items}); |
| 5794 | 5772 | }, |
| 5795 | else => |err| { | |
| 5796 | try err.printError(gpa, &str_buf); | |
| 5797 | log.err("failed parsing {s}: {s}", .{ dep_basename, str_buf.items }); | |
| 5773 | else => |err_token| { | |
| 5774 | log.err("failed parsing {s}: {f}", .{ dep_basename, err_token }); | |
| 5798 | 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 | 5780 | } |
| 5804 | 5781 | |
| 5805 | 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 | 5791 | switch (comp.cache_use) { |
| 5808 | 5792 | .whole => |whole| { |
| 5809 | 5793 | if (whole.cache_manifest) |whole_cache_manifest| { |
| 5810 | 5794 | try whole.cache_manifest_mutex.lock(io); |
| 5811 | 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 | 5805 | .incremental, .none => {}, |
| ... | ... | @@ -5817,16 +5807,28 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr |
| 5817 | 5807 | } |
| 5818 | 5808 | |
| 5819 | 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 | }; | |
| 5821 | 5824 | |
| 5822 | 5825 | // Rename into place. |
| 5823 | const digest = man.final(); | |
| 5824 | const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest }); | |
| 5826 | const o_sub_path = try fs.path.join(arena, &.{ "o", &digest }); | |
| 5825 | 5827 | var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{}); |
| 5826 | 5828 | defer o_dir.close(io); |
| 5827 | 5829 | const tmp_basename = fs.path.basename(out_obj_path); |
| 5828 | 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 | }; |
| 5831 | 5833 | |
| 5832 | 5834 | if (man.have_exclusive_lock) { |
| ... | ... | @@ -5834,11 +5836,8 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr |
| 5834 | 5836 | // possible we had a hit and the manifest is dirty, for example if the file mtime changed but |
| 5835 | 5837 | // the contents were the same, we hit the cache but the manifest is dirty and we need to update |
| 5836 | 5838 | // it to prevent doing a full file content comparison the next time around. |
| 5837 | man.writeManifest() catch |err| { | |
| 5838 | log.warn("failed to write cache manifest when compiling '{s}': {s}", .{ | |
| 5839 | c_object.src.src_path, @errorName(err), | |
| 5840 | }); | |
| 5841 | }; | |
| 5839 | man.finalize() catch |err| | |
| 5840 | log.warn("failed to write cache manifest when compiling {q}: {t}", .{ c_object.src.src_path, err }); | |
| 5842 | 5841 | } |
| 5843 | 5842 | |
| 5844 | 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 | 5899 | // the XML data as a RT_MANIFEST resource. This means we can skip preprocessing, |
| 5901 | 5900 | // include paths, CLI options, etc. |
| 5902 | 5901 | if (win32_resource.src == .manifest) { |
| 5903 | _ = try man.addFilePath(.initCwd(src_path), null); | |
| 5902 | _ = try man.addInputPath(.initCwd(src_path), .{}); | |
| 5904 | 5903 | |
| 5905 | 5904 | const rc_basename = try std.fmt.allocPrint(arena, "{s}.rc", .{src_basename}); |
| 5906 | 5905 | const res_basename = try std.fmt.allocPrint(arena, "{s}.res", .{src_basename}); |
| 5907 | 5906 | |
| 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 | 5916 | // The digest only depends on the .manifest file, so we can |
| 5910 | 5917 | // get the digest now and write the .res directly to the cache |
| 5911 | const digest = man.final(); | |
| 5918 | const digest = man.missDigestHex(); | |
| 5912 | 5919 | |
| 5913 | 5920 | const o_sub_path = try fs.path.join(arena, &.{ "o", &digest }); |
| 5914 | 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 | 5977 | |
| 5971 | 5978 | try spawnZigRc(comp, win32_resource, arena, argv.items, child_progress_node); |
| 5972 | 5979 | |
| 5973 | break :blk digest; | |
| 5980 | break :miss digest; | |
| 5974 | 5981 | }; |
| 5975 | 5982 | |
| 5976 | 5983 | if (man.have_exclusive_lock) { |
| 5977 | man.writeManifest() catch |err| { | |
| 5978 | log.warn("failed to write cache manifest when compiling '{s}': {s}", .{ src_path, @errorName(err) }); | |
| 5979 | }; | |
| 5984 | man.finalize() catch |err| | |
| 5985 | log.warn("failed to write cache manifest when compiling {q}: {t}", .{ src_path, err }); | |
| 5980 | 5986 | } |
| 5981 | 5987 | |
| 5982 | 5988 | win32_resource.status = .{ |
| ... | ... | @@ -5993,12 +5999,20 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 |
| 5993 | 5999 | // We now know that we're compiling an .rc file |
| 5994 | 6000 | const rc_src = win32_resource.src.rc; |
| 5995 | 6001 | |
| 5996 | _ = try man.addFilePath(.initCwd(rc_src.src_path), null); | |
| 6002 | _ = try man.addInputPath(.initCwd(rc_src.src_path), .{}); | |
| 5997 | 6003 | man.hash.addListOfBytes(rc_src.extra_flags); |
| 5998 | 6004 | |
| 5999 | 6005 | const rc_basename_noext = src_basename[0 .. src_basename.len - fs.path.extension(src_basename).len]; |
| 6000 | 6006 | |
| 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 | 6016 | var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, "tmp", .{}); |
| 6003 | 6017 | defer zig_cache_tmp_dir.close(io); |
| 6004 | 6018 | |
| ... | ... | @@ -6055,12 +6069,14 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 |
| 6055 | 6069 | return comp.failWin32Resource(win32_resource, "depfile from zig rc has unexpected format", .{}); |
| 6056 | 6070 | } |
| 6057 | 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 | 6073 | switch (comp.cache_use) { |
| 6060 | 6074 | .whole => |whole| if (whole.cache_manifest) |whole_cache_manifest| { |
| 6061 | 6075 | try whole.cache_manifest_mutex.lock(io); |
| 6062 | 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 | 6081 | .incremental, .none => {}, |
| 6066 | 6082 | } |
| ... | ... | @@ -6068,13 +6084,13 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 |
| 6068 | 6084 | } |
| 6069 | 6085 | |
| 6070 | 6086 | // Rename into place. |
| 6071 | const digest = man.final(); | |
| 6087 | const digest = man.missDigestHex(); | |
| 6072 | 6088 | const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest }); |
| 6073 | 6089 | var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{}); |
| 6074 | 6090 | defer o_dir.close(io); |
| 6075 | 6091 | const tmp_basename = fs.path.basename(out_res_path); |
| 6076 | 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 | }; |
| 6079 | 6095 | |
| 6080 | 6096 | if (man.have_exclusive_lock) { |
| ... | ... | @@ -6082,8 +6098,8 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 |
| 6082 | 6098 | // possible we had a hit and the manifest is dirty, for example if the file mtime changed but |
| 6083 | 6099 | // the contents were the same, we hit the cache but the manifest is dirty and we need to update |
| 6084 | 6100 | // it to prevent doing a full file content comparison the next time around. |
| 6085 | man.writeManifest() catch |err| { | |
| 6086 | log.warn("failed to write cache manifest when compiling '{s}': {s}", .{ rc_src.src_path, @errorName(err) }); | |
| 6101 | man.finalize() catch |err| { | |
| 6102 | log.warn("failed to write cache manifest when compiling {q}: {t}", .{ rc_src.src_path, err }); | |
| 6087 | 6103 | }; |
| 6088 | 6104 | } |
| 6089 | 6105 |
src/Sema.zig+4-8| ... | ... | @@ -12974,7 +12974,7 @@ fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A |
| 12974 | 12974 | |
| 12975 | 12975 | const result = ef_idx.get(zcu); |
| 12976 | 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 | } |
| 12979 | 12979 | |
| 12980 | 12980 | return Air.internedToRef(result.val); |
| ... | ... | @@ -15911,9 +15911,7 @@ fn zirClosureGet(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat |
| 15911 | 15911 | const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu).?; |
| 15912 | 15912 | const tree = file.getTree(zcu) catch |err| { |
| 15913 | 15913 | // In this case we emit a warning + a less precise source location. |
| 15914 | log.warn("unable to load {f}: {s}", .{ | |
| 15915 | file.path.fmt(zcu.comp), @errorName(err), | |
| 15916 | }); | |
| 15914 | log.warn("failed loading {qf}: {t}", .{ file.path.fmt(zcu.comp), err }); | |
| 15917 | 15915 | break :name null; |
| 15918 | 15916 | }; |
| 15919 | 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 | 15937 | const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu).?; |
| 15940 | 15938 | const tree = file.getTree(zcu) catch |err| { |
| 15941 | 15939 | // In this case we emit a warning + a less precise source location. |
| 15942 | log.warn("unable to load {f}: {s}", .{ | |
| 15943 | file.path.fmt(zcu.comp), @errorName(err), | |
| 15944 | }); | |
| 15940 | log.warn("failed loading {qf}: {t}", .{ file.path.fmt(zcu.comp), err }); | |
| 15945 | 15941 | break :name null; |
| 15946 | 15942 | }; |
| 15947 | 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 | 15946 | }; |
| 15951 | 15947 | |
| 15952 | 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 | 15950 | else |
| 15955 | 15951 | try sema.errMsg(src, "variable not accessible from inner function", .{}); |
| 15956 | 15952 | errdefer msg.destroy(sema.gpa); |
src/Zcu.zig+2-2| ... | ... | @@ -974,7 +974,7 @@ pub const File = struct { |
| 974 | 974 | success, |
| 975 | 975 | }, |
| 976 | 976 | /// Whether this is populated depends on `status`. |
| 977 | stat: Cache.File.Stat, | |
| 977 | stat: Cache.Manifest.Stat, | |
| 978 | 978 | |
| 979 | 979 | /// Whether this file is the generated file of a "builtin" module. This matters because those |
| 980 | 980 | /// 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 | 1246 | val: InternPool.Index, |
| 1247 | 1247 | /// If this is `null` and `val` is `.none`, the file has never been loaded. |
| 1248 | 1248 | err: ?(Io.File.OpenError || Io.File.StatError || Io.File.Reader.Error || error{UnexpectedEof}), |
| 1249 | stat: Cache.File.Stat, | |
| 1249 | stat: Cache.Manifest.Stat, | |
| 1250 | 1250 | |
| 1251 | 1251 | pub const Index = enum(u32) { |
| 1252 | 1252 | _, |
src/Zcu/PerThread.zig+22-27| ... | ... | @@ -224,11 +224,12 @@ pub fn update( |
| 224 | 224 | const result = res: { |
| 225 | 225 | try whole.cache_manifest_mutex.lock(io); |
| 226 | 226 | defer whole.cache_manifest_mutex.unlock(io); |
| 227 | if (file.source) |source| { | |
| 228 | break :res file.path.addToCacheManifestPostHitContents(man, &comp.dirs, source, file.stat); | |
| 229 | } else { | |
| 230 | break :res file.path.addToCacheManifestPostHit(man, &comp.dirs); | |
| 231 | } | |
| 227 | break :res file.path.addToCacheManifestAsDiscovered( | |
| 228 | man, | |
| 229 | &comp.dirs, | |
| 230 | file.source, | |
| 231 | if (file.source != null) file.stat else null, | |
| 232 | ); | |
| 232 | 233 | }; |
| 233 | 234 | result catch |err| switch (err) { |
| 234 | 235 | error.OutOfMemory => |e| return e, |
| ... | ... | @@ -348,11 +349,8 @@ pub fn update( |
| 348 | 349 | } |
| 349 | 350 | } |
| 350 | 351 | fn workerUpdateBuiltinFile(comp: *Compilation, file: *Zcu.File) void { |
| 351 | Builtin.updateFileOnDisk(file, comp) catch |err| comp.lockAndSetMiscFailure( | |
| 352 | .write_builtin_zig, | |
| 353 | "unable to write '{f}': {s}", | |
| 354 | .{ file.path.fmt(comp), @errorName(err) }, | |
| 355 | ); | |
| 352 | Builtin.updateFileOnDisk(file, comp) catch |err| | |
| 353 | comp.lockAndSetMiscFailure(.write_builtin_zig, "unable to write {qf}: {t}", .{ file.path.fmt(comp), err }); | |
| 356 | 354 | } |
| 357 | 355 | fn workerUpdateFile( |
| 358 | 356 | comp: *Compilation, |
| ... | ... | @@ -371,7 +369,9 @@ fn workerUpdateFile( |
| 371 | 369 | const active = comp.zcu.?.activate(tid); |
| 372 | 370 | defer active.deactivate(); |
| 373 | 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 | 375 | error.OutOfMemory => { |
| 376 | 376 | comp.mutex.lockUncancelable(io); |
| 377 | 377 | defer comp.mutex.unlock(io); |
| ... | ... | @@ -2836,26 +2836,21 @@ fn updateBuiltinModule(pt: Zcu.PerThread, opts: Builtin) Allocator.Error!struct |
| 2836 | 2836 | assert(mem.eql(u8, import_path, "std")); // the single import is of 'std' |
| 2837 | 2837 | } |
| 2838 | 2838 | |
| 2839 | Builtin.updateFileOnDisk(file, comp) catch |err| comp.setMiscFailure( | |
| 2840 | .write_builtin_zig, | |
| 2841 | "unable to write '{f}': {s}", | |
| 2842 | .{ file.path.fmt(comp), @errorName(err) }, | |
| 2843 | ); | |
| 2839 | Builtin.updateFileOnDisk(file, comp) catch |err| | |
| 2840 | comp.setMiscFailure(.write_builtin_zig, "unable to write {qf}: {t}", .{ file.path.fmt(comp), err }); | |
| 2844 | 2841 | return .{ |
| 2845 | 2842 | .file = file_index, |
| 2846 | 2843 | .module_root = mod, |
| 2847 | 2844 | }; |
| 2848 | 2845 | } |
| 2849 | 2846 | |
| 2847 | pub const EmbedFileError = error{ImportOutsideModulePath} || Allocator.Error || Io.Cancelable; | |
| 2848 | ||
| 2850 | 2849 | pub fn embedFile( |
| 2851 | 2850 | pt: Zcu.PerThread, |
| 2852 | 2851 | cur_file: *Zcu.File, |
| 2853 | 2852 | import_string: []const u8, |
| 2854 | ) error{ | |
| 2855 | OutOfMemory, | |
| 2856 | Canceled, | |
| 2857 | ImportOutsideModulePath, | |
| 2858 | }!Zcu.EmbedFile.Index { | |
| 2853 | ) EmbedFileError!Zcu.EmbedFile.Index { | |
| 2859 | 2854 | const zcu = pt.zcu; |
| 2860 | 2855 | const gpa = zcu.gpa; |
| 2861 | 2856 | |
| ... | ... | @@ -2938,7 +2933,7 @@ fn updateEmbedFileInner( |
| 2938 | 2933 | }; |
| 2939 | 2934 | defer file.close(io); |
| 2940 | 2935 | |
| 2941 | const stat: Cache.File.Stat = .fromFs(try file.stat(io)); | |
| 2936 | const stat: Cache.Manifest.Stat = .init(try file.stat(io)); | |
| 2942 | 2937 | |
| 2943 | 2938 | if (ef.val != .none) { |
| 2944 | 2939 | const old_stat = ef.stat; |
| ... | ... | @@ -2995,10 +2990,7 @@ fn updateEmbedFileInner( |
| 2995 | 2990 | } |
| 2996 | 2991 | |
| 2997 | 2992 | /// Assumes that `path` is allocated into `gpa`. Takes ownership of `path` on success. |
| 2998 | fn newEmbedFile( | |
| 2999 | pt: Zcu.PerThread, | |
| 3000 | path: Compilation.Path, | |
| 3001 | ) !*Zcu.EmbedFile { | |
| 2993 | fn newEmbedFile(pt: Zcu.PerThread, path: Compilation.Path) EmbedFileError!*Zcu.EmbedFile { | |
| 3002 | 2994 | const zcu = pt.zcu; |
| 3003 | 2995 | const comp = zcu.comp; |
| 3004 | 2996 | const io = comp.io; |
| ... | ... | @@ -3035,7 +3027,10 @@ fn newEmbedFile( |
| 3035 | 3027 | try whole.cache_manifest_mutex.lock(io); |
| 3036 | 3028 | defer whole.cache_manifest_mutex.unlock(io); |
| 3037 | 3029 | |
| 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 | } |
| 3040 | 3035 | |
| 3041 | 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 | 458 | man.hash.add(target.abi); |
| 459 | 459 | man.hash.add(target_os_version); |
| 460 | 460 | |
| 461 | const abilists_index = try man.addFilePath(.{ | |
| 461 | const abilists_index = try man.addInputPath(.{ | |
| 462 | 462 | .root_dir = comp.dirs.zig_lib, |
| 463 | 463 | .sub_path = abilists_path, |
| 464 | }, abilists_max_size); | |
| 465 | ||
| 466 | if (try man.hit(prog_node)) { | |
| 467 | const digest = man.final(); | |
| 464 | }, .{ | |
| 465 | .request_contents = true, | |
| 466 | }); | |
| 468 | 467 | |
| 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 | 483 | return queueSharedObjects(comp, .{ |
| 470 | 484 | .lock = man.toOwnedLock(), |
| 471 | 485 | .dir_path = .{ |
| ... | ... | @@ -475,7 +489,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 475 | 489 | }); |
| 476 | 490 | } |
| 477 | 491 | |
| 478 | const digest = man.final(); | |
| 492 | const digest = man.missDigestHex(); | |
| 479 | 493 | const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest }); |
| 480 | 494 | |
| 481 | 495 | var o_directory: Cache.Directory = .{ |
| ... | ... | @@ -484,7 +498,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 484 | 498 | }; |
| 485 | 499 | defer o_directory.handle.close(io); |
| 486 | 500 | |
| 487 | const abilists_contents = man.files.keys()[abilists_index].contents.?; | |
| 501 | const abilists_contents = abilists_index.contents(&man); | |
| 488 | 502 | const metadata = try loadMetaData(gpa, abilists_contents); |
| 489 | 503 | defer metadata.destroy(gpa); |
| 490 | 504 | |
| ... | ... | @@ -980,9 +994,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 980 | 994 | try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node); |
| 981 | 995 | } |
| 982 | 996 | |
| 983 | man.writeManifest() catch |err| { | |
| 984 | log.warn("failed to write cache manifest for FreeBSD libc stubs: {s}", .{@errorName(err)}); | |
| 985 | }; | |
| 997 | man.finalize() catch |err| log.warn("failed to write cache manifest for FreeBSD libc stubs: {t}", .{err}); | |
| 986 | 998 | |
| 987 | 999 | return queueSharedObjects(comp, .{ |
| 988 | 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 | 698 | man.hash.add(target.abi); |
| 699 | 699 | man.hash.add(target_version); |
| 700 | 700 | |
| 701 | const abilists_index = try man.addFilePath(.{ | |
| 701 | const abilists_index = try man.addInputPath(.{ | |
| 702 | 702 | .root_dir = comp.dirs.zig_lib, |
| 703 | 703 | .sub_path = abilists_path, |
| 704 | }, abilists_max_size); | |
| 705 | ||
| 706 | if (try man.hit(prog_node)) { | |
| 707 | const digest = man.final(); | |
| 704 | }, .{ | |
| 705 | .request_contents = true, | |
| 706 | }); | |
| 708 | 707 | |
| 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 | 723 | return queueSharedObjects(comp, .{ |
| 710 | 724 | .lock = man.toOwnedLock(), |
| 711 | 725 | .dir_path = .{ |
| ... | ... | @@ -715,8 +729,8 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 715 | 729 | }); |
| 716 | 730 | } |
| 717 | 731 | |
| 718 | const digest = man.final(); | |
| 719 | const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest }); | |
| 732 | const digest = man.missDigestHex(); | |
| 733 | const o_sub_path = try path.join(arena, &.{ "o", &digest }); | |
| 720 | 734 | |
| 721 | 735 | var o_directory: Cache.Directory = .{ |
| 722 | 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 | 738 | }; |
| 725 | 739 | defer o_directory.handle.close(io); |
| 726 | 740 | |
| 727 | const abilists_contents = man.files.keys()[abilists_index].contents.?; | |
| 741 | const abilists_contents = abilists_index.contents(&man); | |
| 728 | 742 | const metadata = try loadMetaData(gpa, abilists_contents); |
| 729 | 743 | defer metadata.destroy(gpa); |
| 730 | 744 | |
| ... | ... | @@ -1129,9 +1143,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 1129 | 1143 | try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node); |
| 1130 | 1144 | } |
| 1131 | 1145 | |
| 1132 | man.writeManifest() catch |err| { | |
| 1133 | log.warn("failed to write cache manifest for glibc stubs: {s}", .{@errorName(err)}); | |
| 1134 | }; | |
| 1146 | man.finalize() catch |err| log.warn("failed to write cache manifest for glibc stubs: {t}", .{err}); | |
| 1135 | 1147 | |
| 1136 | 1148 | return queueSharedObjects(comp, .{ |
| 1137 | 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 | 246 | var man = cache.obtain(); |
| 247 | 247 | defer man.deinit(); |
| 248 | 248 | |
| 249 | _ = try man.addFilePath(def_file_path, null); | |
| 249 | _ = try man.addInputPath(def_file_path, .{}); | |
| 250 | 250 | |
| 251 | 251 | const final_lib_basename = try std.fmt.allocPrint(gpa, "{s}.lib", .{lib_name}); |
| 252 | 252 | errdefer gpa.free(final_lib_basename); |
| 253 | 253 | |
| 254 | const is_hit = man.hit(prog_node) catch |err| switch (err) { | |
| 255 | error.CacheCheckFailed => switch (man.diagnostic) { | |
| 256 | .none => unreachable, | |
| 257 | .manifest_create, .manifest_read, .manifest_lock => |e| { | |
| 258 | comp.setMiscFailure(.windows_import_lib, "checking cache failed: {t} {t}", .{ man.diagnostic, e }); | |
| 259 | return error.AlreadyReported; | |
| 260 | }, | |
| 261 | .file_open, .file_stat, .file_read, .file_hash => |op| { | |
| 262 | const pp = man.files.keys()[op.file_index].prefixed_path; | |
| 263 | const prefix = man.cache.prefixes()[pp.prefix]; | |
| 264 | comp.setMiscFailure(.windows_import_lib, "checking cache failed: {f}{s} {t} {t}", .{ | |
| 265 | prefix, pp.sub_path, man.diagnostic, op.err, | |
| 266 | }); | |
| 267 | return error.AlreadyReported; | |
| 268 | }, | |
| 269 | }, | |
| 270 | error.OutOfMemory, error.Canceled => |e| return e, | |
| 271 | error.InvalidFormat => { | |
| 272 | comp.setMiscFailure(.windows_import_lib, "checking cache failed: invalid manifest file format", .{}); | |
| 254 | var diag: Cache.Manifest.CheckDiagnostic = undefined; | |
| 255 | const status = man.check(&diag, prog_node) catch |err| switch (err) { | |
| 256 | error.CacheCheckFailed => { | |
| 257 | comp.lockAndSetMiscFailure(.windows_import_lib, "{s} cache check failed: {f}", .{ | |
| 258 | final_lib_basename, diag.fmt(&man), | |
| 259 | }); | |
| 273 | 260 | return error.AlreadyReported; |
| 274 | 261 | }, |
| 262 | error.OutOfMemory, error.Canceled => |e| return e, | |
| 275 | 263 | }; |
| 276 | if (is_hit) { | |
| 277 | const digest = man.final(); | |
| 264 | log.debug("{s} cache {f}", .{ final_lib_basename, status.fmt(&man) }); | |
| 265 | if (status == .hit) { | |
| 266 | const digest = man.hitDigestHex(); | |
| 278 | 267 | const sub_path = try std.fs.path.join(gpa, &.{ "o", &digest, final_lib_basename }); |
| 279 | 268 | errdefer gpa.free(sub_path); |
| 280 | 269 | |
| ... | ... | @@ -293,7 +282,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8, prog_node: std.P |
| 293 | 282 | return crt_file_path; |
| 294 | 283 | } |
| 295 | 284 | |
| 296 | const digest = man.final(); | |
| 285 | const digest = man.missDigestHex(); | |
| 297 | 286 | const o_sub_path = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest }); |
| 298 | 287 | var o_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, o_sub_path, .{}); |
| 299 | 288 | defer o_dir.close(io); |
| ... | ... | @@ -365,9 +354,8 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8, prog_node: std.P |
| 365 | 354 | try file_writer.interface.flush(); |
| 366 | 355 | } |
| 367 | 356 | |
| 368 | man.writeManifest() catch |err| { | |
| 369 | log.warn("failed to write cache manifest for DLL import {s}.lib: {s}", .{ lib_name, @errorName(err) }); | |
| 370 | }; | |
| 357 | man.finalize() catch |err| | |
| 358 | log.warn("failed to write cache manifest for DLL import {s}.lib: {t}", .{ lib_name, err }); | |
| 371 | 359 | |
| 372 | 360 | comp.mutex.lockUncancelable(io); |
| 373 | 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 | 406 | man.hash.add(target.abi); |
| 407 | 407 | man.hash.add(target_version); |
| 408 | 408 | |
| 409 | const abilists_index = try man.addFilePath(.{ | |
| 409 | const abilists_index = try man.addInputPath(.{ | |
| 410 | 410 | .root_dir = comp.dirs.zig_lib, |
| 411 | 411 | .sub_path = abilists_path, |
| 412 | }, abilists_max_size); | |
| 413 | ||
| 414 | if (try man.hit(prog_node)) { | |
| 415 | const digest = man.final(); | |
| 412 | }, .{ | |
| 413 | .request_contents = true, | |
| 414 | }); | |
| 416 | 415 | |
| 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 | 431 | return queueSharedObjects(comp, .{ |
| 418 | 432 | .lock = man.toOwnedLock(), |
| 419 | 433 | .dir_path = .{ |
| ... | ... | @@ -423,8 +437,8 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 423 | 437 | }); |
| 424 | 438 | } |
| 425 | 439 | |
| 426 | const digest = man.final(); | |
| 427 | const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest }); | |
| 440 | const digest = man.missDigestHex(); | |
| 441 | const o_sub_path = try path.join(arena, &.{ "o", &digest }); | |
| 428 | 442 | |
| 429 | 443 | var o_directory: Cache.Directory = .{ |
| 430 | 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 | 446 | }; |
| 433 | 447 | defer o_directory.handle.close(io); |
| 434 | 448 | |
| 435 | const abilists_contents = man.files.keys()[abilists_index].contents.?; | |
| 449 | const abilists_contents = abilists_index.contents(&man); | |
| 436 | 450 | const metadata = try loadMetaData(gpa, abilists_contents); |
| 437 | 451 | defer metadata.destroy(gpa); |
| 438 | 452 | |
| ... | ... | @@ -641,9 +655,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 641 | 655 | try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node); |
| 642 | 656 | } |
| 643 | 657 | |
| 644 | man.writeManifest() catch |err| { | |
| 645 | log.warn("failed to write cache manifest for NetBSD libc stubs: {s}", .{@errorName(err)}); | |
| 646 | }; | |
| 658 | man.finalize() catch |err| log.warn("failed to write cache manifest for NetBSD libc stubs: {t}", .{err}); | |
| 647 | 659 | |
| 648 | 660 | return queueSharedObjects(comp, .{ |
| 649 | 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 | 327 | man.hash.add(target.abi); |
| 328 | 328 | man.hash.add(target_version); |
| 329 | 329 | |
| 330 | const abilists_index = try man.addFilePath(.{ | |
| 330 | const abilists_index = try man.addInputPath(.{ | |
| 331 | 331 | .root_dir = comp.dirs.zig_lib, |
| 332 | 332 | .sub_path = abilists_path, |
| 333 | }, abilists_max_size); | |
| 334 | ||
| 335 | if (try man.hit(prog_node)) { | |
| 336 | const digest = man.final(); | |
| 333 | }, .{ | |
| 334 | .request_contents = true, | |
| 335 | }); | |
| 337 | 336 | |
| 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 | 352 | return queueSharedObjects(comp, .{ |
| 339 | 353 | .lock = man.toOwnedLock(), |
| 340 | 354 | .dir_path = .{ |
| ... | ... | @@ -344,7 +358,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 344 | 358 | }); |
| 345 | 359 | } |
| 346 | 360 | |
| 347 | const digest = man.final(); | |
| 361 | const digest = man.missDigestHex(); | |
| 348 | 362 | const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest }); |
| 349 | 363 | |
| 350 | 364 | var o_directory: Cache.Directory = .{ |
| ... | ... | @@ -353,7 +367,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 353 | 367 | }; |
| 354 | 368 | defer o_directory.handle.close(io); |
| 355 | 369 | |
| 356 | const abilists_contents = man.files.keys()[abilists_index].contents.?; | |
| 370 | const abilists_contents = abilists_index.contents(&man); | |
| 357 | 371 | const metadata = try loadMetaData(gpa, abilists_contents); |
| 358 | 372 | defer metadata.destroy(gpa); |
| 359 | 373 | |
| ... | ... | @@ -562,9 +576,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye |
| 562 | 576 | try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node); |
| 563 | 577 | } |
| 564 | 578 | |
| 565 | man.writeManifest() catch |err| { | |
| 566 | log.warn("failed to write cache manifest for OpenBSD libc stubs: {s}", .{@errorName(err)}); | |
| 567 | }; | |
| 579 | man.finalize() catch |err| log.warn("failed to write cache manifest for OpenBSD libc stubs: {t}", .{err}); | |
| 568 | 580 | |
| 569 | 581 | return queueSharedObjects(comp, .{ |
| 570 | 582 | .lock = man.toOwnedLock(), |
src/link.zig+17-8| ... | ... | @@ -1500,11 +1500,11 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { |
| 1500 | 1500 | base.loadInput(input) catch |err| switch (err) { |
| 1501 | 1501 | error.AlreadyReported => return, // error reported via diags |
| 1502 | 1502 | else => |e| switch (input) { |
| 1503 | .dso => |dso| diags.addParseError(dso.path, "failed to parse shared library: {s}", .{@errorName(e)}), | |
| 1504 | .object => |obj| diags.addParseError(obj.path, "failed to parse object: {s}", .{@errorName(e)}), | |
| 1505 | .archive => |obj| diags.addParseError(obj.path, "failed to parse archive: {s}", .{@errorName(e)}), | |
| 1506 | .res => |res| diags.addParseError(res.path, "failed to parse Windows resource: {s}", .{@errorName(e)}), | |
| 1507 | .dso_exact => diags.addError("failed to handle dso_exact: {s}", .{@errorName(e)}), | |
| 1503 | .dso => |dso| diags.addParseError(dso.path, "failed to parse shared library: {t}", .{e}), | |
| 1504 | .object => |obj| diags.addParseError(obj.path, "failed to parse object: {t}", .{e}), | |
| 1505 | .archive => |obj| diags.addParseError(obj.path, "failed to parse archive: {t}", .{e}), | |
| 1506 | .res => |res| diags.addParseError(res.path, "failed to parse Windows resource: {t}", .{e}), | |
| 1507 | .dso_exact => diags.addError("failed to handle dso_exact: {t}", .{e}), | |
| 1508 | 1508 | }, |
| 1509 | 1509 | }; |
| 1510 | 1510 | prog_node.completeOne(); |
| ... | ... | @@ -1925,15 +1925,24 @@ pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void { |
| 1925 | 1925 | man.hash.add(@as(@typeInfo(Input).@"union".tag_type.?, link_input)); |
| 1926 | 1926 | switch (link_input) { |
| 1927 | 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 | 1932 | man.hash.add(obj.must_link); |
| 1930 | 1933 | man.hash.add(obj.hidden); |
| 1931 | 1934 | }, |
| 1932 | 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 | 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 | 1946 | man.hash.add(dso.needed); |
| 1938 | 1947 | man.hash.add(dso.weak); |
| 1939 | 1948 | man.hash.add(dso.reexport); |
src/link/Elf.zig+2-2| ... | ... | @@ -1120,7 +1120,7 @@ fn parseDso( |
| 1120 | 1120 | |
| 1121 | 1121 | const handle = dso.file; |
| 1122 | 1122 | |
| 1123 | const stat = Stat.fromFs(try handle.stat(io)); | |
| 1123 | const stat: Stat = .init(try handle.stat(io)); | |
| 1124 | 1124 | var header = try SharedObject.parseHeader(gpa, io, diags, dso.path, handle, stat, target); |
| 1125 | 1125 | defer header.deinit(gpa); |
| 1126 | 1126 | |
| ... | ... | @@ -4407,7 +4407,7 @@ const mem = std.mem; |
| 4407 | 4407 | const Allocator = std.mem.Allocator; |
| 4408 | 4408 | const Hash = std.hash.Wyhash; |
| 4409 | 4409 | const Path = std.Build.Cache.Path; |
| 4410 | const Stat = std.Build.Cache.File.Stat; | |
| 4410 | const Stat = std.Build.Cache.Manifest.Stat; | |
| 4411 | 4411 | |
| 4412 | 4412 | const codegen = @import("../codegen.zig"); |
| 4413 | 4413 | const eh_frame = @import("Elf/eh_frame.zig"); |
src/link/Elf/SharedObject.zig+1-1| ... | ... | @@ -7,7 +7,7 @@ const elf = std.elf; |
| 7 | 7 | const log = std.log.scoped(.elf); |
| 8 | 8 | const mem = std.mem; |
| 9 | 9 | const Path = std.Build.Cache.Path; |
| 10 | const Stat = std.Build.Cache.File.Stat; | |
| 10 | const Stat = std.Build.Cache.Manifest.Stat; | |
| 11 | 11 | const Allocator = mem.Allocator; |
| 12 | 12 | |
| 13 | 13 | const 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 | 155 | for (hm) |value| { |
| 156 | 156 | man.hash.add(value.needed); |
| 157 | 157 | man.hash.add(value.weak); |
| 158 | _ = try man.addFilePath(value.path, null); | |
| 158 | _ = try man.addInputPath(value.path, .{}); | |
| 159 | 159 | } |
| 160 | 160 | } |
| 161 | 161 |
src/main.zig+16-7| ... | ... | @@ -107,6 +107,7 @@ const normal_usage = |
| 107 | 107 | \\ |
| 108 | 108 | \\ env Print lib path, std path, cache directory, and version |
| 109 | 109 | \\ help Print this help and exit |
| 110 | \\ cache-cat Print a zig-cache manifest file as zon | |
| 110 | 111 | \\ std View standard library documentation in a browser |
| 111 | 112 | \\ libc Display native libc paths file or validate one |
| 112 | 113 | \\ targets List available compilation targets |
| ... | ... | @@ -242,6 +243,10 @@ const Cmd = enum { |
| 242 | 243 | ar, |
| 243 | 244 | |
| 244 | 245 | build, |
| 246 | @"cache-cat", | |
| 247 | fetch, | |
| 248 | init, | |
| 249 | libc, | |
| 245 | 250 | |
| 246 | 251 | clang, |
| 247 | 252 | @"-cc1", |
| ... | ... | @@ -258,10 +263,7 @@ const Cmd = enum { |
| 258 | 263 | fmt, |
| 259 | 264 | objcopy, |
| 260 | 265 | objdump, |
| 261 | fetch, | |
| 262 | libc, | |
| 263 | 266 | std, |
| 264 | init, | |
| 265 | 267 | targets, |
| 266 | 268 | version, |
| 267 | 269 | env, |
| ... | ... | @@ -351,7 +353,7 @@ fn mainArgs( |
| 351 | 353 | dev.check(.ar_command); |
| 352 | 354 | return process.exit(try llvmArMain(arena, args)); |
| 353 | 355 | }, |
| 354 | .build, .fetch, .init, .libc => { | |
| 356 | .build, .fetch, .init, .libc, .@"cache-cat" => { | |
| 355 | 357 | return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ |
| 356 | 358 | .cmd_name = "maker", |
| 357 | 359 | .root_src_path = "Maker.zig", |
| ... | ... | @@ -3268,6 +3270,7 @@ fn buildOutputType( |
| 3268 | 3270 | }; |
| 3269 | 3271 | |
| 3270 | 3272 | const cwd_path = try std.zig.getResolvedCwd(io, arena); |
| 3273 | std.log.debug("cwd_path={s}", .{cwd_path}); | |
| 3271 | 3274 | |
| 3272 | 3275 | // This `init` calls `fatal` on error. |
| 3273 | 3276 | var dirs: std.zig.Directories = .init(arena, io, .{ |
| ... | ... | @@ -4876,8 +4879,14 @@ fn cmdTranslateC( |
| 4876 | 4879 | Compilation.cache_helpers.hashCSource(&man, c_source_file) catch |err| |
| 4877 | 4880 | fatal("unable to process {q}: {t}", .{ c_source_file.src_path, err }); |
| 4878 | 4881 | |
| 4879 | const result: Compilation.TranslateCResult = if (try man.hit(prog_node)) .{ | |
| 4880 | .digest = man.finalBin(), | |
| 4882 | var diag: Cache.Manifest.CheckDiagnostic = undefined; | |
| 4883 | const status = man.check(&diag, prog_node) catch |err| switch (err) { | |
| 4884 | error.OutOfMemory, error.Canceled => |e| return e, | |
| 4885 | error.CacheCheckFailed => fatal("translate-c checking cache failed: {f}", .{diag.fmt(&man)}), | |
| 4886 | }; | |
| 4887 | std.log.debug("translate-c cache {f}", .{status.fmt(&man)}); | |
| 4888 | const result: Compilation.TranslateCResult = if (status == .hit) .{ | |
| 4889 | .digest = man.hitDigest(), | |
| 4881 | 4890 | .cache_hit = true, |
| 4882 | 4891 | .errors = std.zig.ErrorBundle.empty, |
| 4883 | 4892 | } else result: { |
| ... | ... | @@ -4904,7 +4913,7 @@ fn cmdTranslateC( |
| 4904 | 4913 | } |
| 4905 | 4914 | } |
| 4906 | 4915 | |
| 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 | 4917 | break :result result; |
| 4909 | 4918 | }; |
| 4910 | 4919 |
test/cases/compile_errors/accessing_runtime_parameter_from_outer_function.zig+1-1| ... | ... | @@ -14,5 +14,5 @@ export fn entry() void { |
| 14 | 14 | |
| 15 | 15 | // error |
| 16 | 16 | // |
| 17 | // :4:24: error: 'y' not accessible from inner function | |
| 17 | // :4:24: error: "y" not accessible from inner function | |
| 18 | 18 | // :3:9: note: crossed function definition here |
test/cases/compile_errors/bad_import.zig+1-1| ... | ... | @@ -4,5 +4,5 @@ const bogus = @import( |
| 4 | 4 | |
| 5 | 5 | // error |
| 6 | 6 | // |
| 7 | // bogus-does-not-exist.zig:1:1: error: unable to load 'bogus-does-not-exist.zig': FileNotFound | |
| 7 | // bogus-does-not-exist.zig:1:1: error: unable to load "bogus-does-not-exist.zig": FileNotFound | |
| 8 | 8 | // :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 | 6 | |
| 7 | 7 | // error |
| 8 | 8 | // |
| 9 | // :1:29: error: unable to open 'bogus.txt': FileNotFound | |
| 9 | // :1:29: error: failed opening "bogus.txt": FileNotFound |
test/incremental/bad_import+2-2| ... | ... | @@ -10,7 +10,7 @@ const io = std.Io.Threaded.global_single_threaded.io(); |
| 10 | 10 | comptime { |
| 11 | 11 | _ = @import("bad.zig"); |
| 12 | 12 | } |
| 13 | #expect_error=bad.zig:1:1: error: unable to load 'bad.zig': FileNotFound | |
| 13 | #expect_error=bad.zig:1:1: error: unable to load "bad.zig": FileNotFound | |
| 14 | 14 | #expect_error=foo.zig:2:17: note: file imported here |
| 15 | 15 | |
| 16 | 16 | #update=change bad import |
| ... | ... | @@ -18,7 +18,7 @@ comptime { |
| 18 | 18 | comptime { |
| 19 | 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': FileNotFound | |
| 21 | #expect_error=this_is/not_real.zig:1:1: error: unable to load "not_real.zig": FileNotFound | |
| 22 | 22 | #expect_error=foo.zig:2:17: note: file imported here |
| 23 | 23 | |
| 24 | 24 | #update=remove import of 'foo.zig' |
test/incremental/change_embed_file+2-2| ... | ... | @@ -17,7 +17,7 @@ Hello again, World! |
| 17 | 17 | |
| 18 | 18 | #update=delete file |
| 19 | 19 | #rm_file=string.txt |
| 20 | #expect_error=main.zig:2:27: error: unable to open 'string.txt': FileNotFound | |
| 20 | #expect_error=main.zig:2:27: error: failed opening "string.txt": FileNotFound | |
| 21 | 21 | |
| 22 | 22 | #update=remove reference to file |
| 23 | 23 | #file=main.zig |
| ... | ... | @@ -37,7 +37,7 @@ pub fn main() !void { |
| 37 | 37 | try std.Io.File.stdout().writeStreamingAll(io, string); |
| 38 | 38 | } |
| 39 | 39 | const io = std.Io.Threaded.global_single_threaded.io(); |
| 40 | #expect_error=main.zig:2:27: error: unable to open 'string.txt': FileNotFound | |
| 40 | #expect_error=main.zig:2:27: error: failed opening "string.txt": FileNotFound | |
| 41 | 41 | |
| 42 | 42 | #update=recreate file |
| 43 | 43 | #file=string.txt |
test/incremental/change_zon_file+2-2| ... | ... | @@ -17,7 +17,7 @@ const io = std.Io.Threaded.global_single_threaded.io(); |
| 17 | 17 | |
| 18 | 18 | #update=delete file |
| 19 | 19 | #rm_file=message.zon |
| 20 | #expect_error=message.zon:1:1: error: unable to load 'message.zon': FileNotFound | |
| 20 | #expect_error=message.zon:1:1: error: unable to load "message.zon": FileNotFound | |
| 21 | 21 | #expect_error=main.zig:2:37: note: file imported here |
| 22 | 22 | |
| 23 | 23 | #update=remove reference to ZON file |
| ... | ... | @@ -28,7 +28,7 @@ pub fn main() !void { |
| 28 | 28 | try std.Io.File.stdout().writeStreamingAll(io, "a hardcoded string\n"); |
| 29 | 29 | } |
| 30 | 30 | const io = std.Io.Threaded.global_single_threaded.io(); |
| 31 | #expect_error=message.zon:1:1: error: unable to load 'message.zon': FileNotFound | |
| 31 | #expect_error=message.zon:1:1: error: unable to load "message.zon": FileNotFound | |
| 32 | 32 | #expect_error=main.zig:2:37: note: file imported here |
| 33 | 33 | |
| 34 | 34 | #update=recreate ZON file |
test/src/Cases.zig+2-2| ... | ... | @@ -319,7 +319,7 @@ pub fn addCompile( |
| 319 | 319 | pub fn addFromDir(ctx: *Cases, dir: Io.Dir, path_from_root: []const u8, b: *std.Build) void { |
| 320 | 320 | var current_file: []const u8 = "none"; |
| 321 | 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 | 323 | current_file, err, |
| 324 | 324 | }); |
| 325 | 325 | }; |
| ... | ... | @@ -348,7 +348,7 @@ fn addFromDirInner( |
| 348 | 348 | try filenames.append(ctx.arena, try ctx.arena.dupe(u8, entry.path)); |
| 349 | 349 | }, |
| 350 | 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 | 353 | else => continue, |
| 354 | 354 | } |
test/tests.zig+3-3| ... | ... | @@ -3339,7 +3339,7 @@ pub fn addCases( |
| 3339 | 3339 | |
| 3340 | 3340 | var cases = @import("src/Cases.zig").init(gpa, arena, io); |
| 3341 | 3341 | |
| 3342 | b.dependOnDirectory(b.path("test/cases")); | |
| 3342 | b.dependOnDirectoryContents(b.path("test/cases")); | |
| 3343 | 3343 | |
| 3344 | 3344 | var dir = try b.root.openDir(io, "test/cases", .{ .iterate = true }); |
| 3345 | 3345 | defer dir.close(io); |
| ... | ... | @@ -3401,7 +3401,7 @@ pub fn addIncrementalTests( |
| 3401 | 3401 | }), |
| 3402 | 3402 | }); |
| 3403 | 3403 | |
| 3404 | b.dependOnDirectory(b.path("test/incremental")); | |
| 3404 | b.dependOnDirectoryContents(b.path("test/incremental")); | |
| 3405 | 3405 | |
| 3406 | 3406 | var dir = try b.root.openDir(io, "test/incremental", .{ .iterate = true }); |
| 3407 | 3407 | defer dir.close(io); |
| ... | ... | @@ -3417,7 +3417,7 @@ pub fn addIncrementalTests( |
| 3417 | 3417 | switch (entry.kind) { |
| 3418 | 3418 | .file => {}, |
| 3419 | 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 | 3422 | else => continue, |
| 3423 | 3423 | } |
tools/incr-check.zig+32-20| ... | ... | @@ -152,18 +152,12 @@ pub fn main(init: std.process.Init) !void { |
| 152 | 152 | |
| 153 | 153 | var child_args: std.ArrayList([]const u8) = .empty; |
| 154 | 154 | try child_args.appendSlice(arena, &.{ |
| 155 | resolved_zig_exe, | |
| 156 | "build-exe", | |
| 157 | "-fincremental", | |
| 158 | "-fno-ubsan-rt", | |
| 159 | "-target", | |
| 160 | try target_query.zigTriple(arena), | |
| 161 | "--cache-dir", | |
| 162 | ".local-cache", | |
| 163 | "--global-cache-dir", | |
| 164 | ".global-cache", | |
| 155 | resolved_zig_exe, "build-exe", | |
| 156 | "-fincremental", "-fno-ubsan-rt", | |
| 157 | "-target", try target_query.zigTriple(arena), | |
| 158 | "--cache-dir", ".local-cache", | |
| 159 | "--global-cache-dir", ".global-cache", | |
| 165 | 160 | }); |
| 166 | try child_args.append(arena, "--listen=-"); | |
| 167 | 161 | |
| 168 | 162 | if (opt_resolved_lib_dir) |resolved_lib_dir| { |
| 169 | 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 | 181 | for (case.modules) |mod| { |
| 188 | 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=-"); | |
| 190 | 185 | |
| 191 | 186 | const zig_prog_node = prog_node.start("zig", 0); |
| 192 | 187 | defer zig_prog_node.end(); |
| ... | ... | @@ -199,15 +194,23 @@ pub fn main(init: std.process.Init) !void { |
| 199 | 194 | resolved_zig_exe; |
| 200 | 195 | |
| 201 | 196 | try cc_child_args.appendSlice(arena, &.{ |
| 202 | resolved_cc_zig_exe, | |
| 203 | "cc", | |
| 204 | "-target", | |
| 205 | try target_query.zigTriple(arena), | |
| 206 | "-I", | |
| 207 | opt_resolved_lib_dir.?, // verified earlier | |
| 197 | resolved_cc_zig_exe, "build-exe", | |
| 198 | "-target", try target_query.zigTriple(arena), | |
| 199 | "--cache-dir", ".local-cache", | |
| 200 | "--global-cache-dir", ".global-cache", | |
| 201 | "-I", opt_resolved_lib_dir.?, // verified earlier | |
| 202 | "-lc", | |
| 208 | 203 | }); |
| 204 | } | |
| 205 | ||
| 206 | const allow_compiler_stderr = debug_log_args.items.len != 0; | |
| 209 | 207 | |
| 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 | } |
| 212 | 215 | |
| 213 | 216 | var child = try std.process.spawn(io, .{ |
| ... | ... | @@ -233,7 +236,7 @@ pub fn main(init: std.process.Init) !void { |
| 233 | 236 | .tmp_dir = tmp_dir, |
| 234 | 237 | .tmp_dir_path = tmp_dir_path, |
| 235 | 238 | .child = &child, |
| 236 | .allow_compiler_stderr = debug_log_args.items.len != 0, | |
| 239 | .allow_compiler_stderr = allow_compiler_stderr, | |
| 237 | 240 | .quiet = quiet, |
| 238 | 241 | .preserve_tmp_on_fatal = preserve_tmp, |
| 239 | 242 | .cc_child_args = &cc_child_args, |
| ... | ... | @@ -659,9 +662,18 @@ const Eval = struct { |
| 659 | 662 | const child_prog_node = prog_node.start("build cbe output", 0); |
| 660 | 663 | defer child_prog_node.end(); |
| 661 | 664 | |
| 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 | 667 | defer eval.cc_child_args.items.len -= 2; |
| 664 | 668 | |
| 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 | 677 | const result = std.process.run(eval.arena, eval.io, .{ |
| 666 | 678 | .argv = eval.cc_child_args.items, |
| 667 | 679 | .cwd = .{ .path = eval.tmp_dir_path }, |