| ... | @@ -1,3 +1,7 @@ | ... | @@ -1,3 +1,7 @@ |
| | 1 | //! Manages `zig-cache` directories. |
| | 2 | //! This is not a general-purpose cache. It is designed to be fast and simple, |
| | 3 | //! not to withstand attacks using specially-crafted input. |
| | 4 | |
| 1 | gpa: Allocator, | 5 | gpa: Allocator, |
| 2 | manifest_dir: fs.Dir, | 6 | manifest_dir: fs.Dir, |
| 3 | hash: HashHelper = .{}, | 7 | hash: HashHelper = .{}, |
| ... | @@ -5,6 +9,14 @@ hash: HashHelper = .{}, | ... | @@ -5,6 +9,14 @@ hash: HashHelper = .{}, |
| 5 | recent_problematic_timestamp: i128 = 0, | 9 | recent_problematic_timestamp: i128 = 0, |
| 6 | mutex: std.Thread.Mutex = .{}, | 10 | mutex: std.Thread.Mutex = .{}, |
| 7 | | 11 | |
| | 12 | /// A set of strings such as the zig library directory or project source root, which |
| | 13 | /// are stripped from the file paths before putting into the cache. They |
| | 14 | /// are replaced with single-character indicators. This is not to save |
| | 15 | /// space but to eliminate absolute file paths. This improves portability |
| | 16 | /// and usefulness of the cache for advanced use cases. |
| | 17 | prefixes_buffer: [3]Compilation.Directory = undefined, |
| | 18 | prefixes_len: usize = 0, |
| | 19 | |
| 8 | const Cache = @This(); | 20 | const Cache = @This(); |
| 9 | const std = @import("std"); | 21 | const std = @import("std"); |
| 10 | const builtin = @import("builtin"); | 22 | const builtin = @import("builtin"); |
| ... | @@ -18,6 +30,11 @@ const Allocator = std.mem.Allocator; | ... | @@ -18,6 +30,11 @@ const Allocator = std.mem.Allocator; |
| 18 | const Compilation = @import("Compilation.zig"); | 30 | const Compilation = @import("Compilation.zig"); |
| 19 | const log = std.log.scoped(.cache); | 31 | const log = std.log.scoped(.cache); |
| 20 | | 32 | |
| | 33 | pub fn addPrefix(cache: *Cache, directory: Compilation.Directory) void { |
| | 34 | cache.prefixes_buffer[cache.prefixes_len] = directory; |
| | 35 | cache.prefixes_len += 1; |
| | 36 | } |
| | 37 | |
| 21 | /// Be sure to call `Manifest.deinit` after successful initialization. | 38 | /// Be sure to call `Manifest.deinit` after successful initialization. |
| 22 | pub fn obtain(cache: *Cache) Manifest { | 39 | pub fn obtain(cache: *Cache) Manifest { |
| 23 | return Manifest{ | 40 | return Manifest{ |
| ... | @@ -29,6 +46,48 @@ pub fn obtain(cache: *Cache) Manifest { | ... | @@ -29,6 +46,48 @@ pub fn obtain(cache: *Cache) Manifest { |
| 29 | }; | 46 | }; |
| 30 | } | 47 | } |
| 31 | | 48 | |
| | 49 | pub fn prefixes(cache: *const Cache) []const Compilation.Directory { |
| | 50 | return cache.prefixes_buffer[0..cache.prefixes_len]; |
| | 51 | } |
| | 52 | |
| | 53 | const PrefixedPath = struct { |
| | 54 | prefix: u8, |
| | 55 | sub_path: []u8, |
| | 56 | }; |
| | 57 | |
| | 58 | fn findPrefix(cache: *const Cache, file_path: []const u8) !PrefixedPath { |
| | 59 | const gpa = cache.gpa; |
| | 60 | const resolved_path = try fs.path.resolve(gpa, &[_][]const u8{file_path}); |
| | 61 | errdefer gpa.free(resolved_path); |
| | 62 | return findPrefixResolved(cache, resolved_path); |
| | 63 | } |
| | 64 | |
| | 65 | /// Takes ownership of `resolved_path` on success. |
| | 66 | fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath { |
| | 67 | const gpa = cache.gpa; |
| | 68 | const prefixes_slice = cache.prefixes(); |
| | 69 | var i: u8 = 1; // Start at 1 to skip over checking the null prefix. |
| | 70 | while (i < prefixes_slice.len) : (i += 1) { |
| | 71 | const p = prefixes_slice[i].path.?; |
| | 72 | if (mem.startsWith(u8, resolved_path, p)) { |
| | 73 | // +1 to skip over the path separator here |
| | 74 | const sub_path = try gpa.dupe(u8, resolved_path[p.len + 1 ..]); |
| | 75 | gpa.free(resolved_path); |
| | 76 | return PrefixedPath{ |
| | 77 | .prefix = @intCast(u8, i), |
| | 78 | .sub_path = sub_path, |
| | 79 | }; |
| | 80 | } else { |
| | 81 | log.debug("'{s}' does not start with '{s}'", .{ resolved_path, p }); |
| | 82 | } |
| | 83 | } |
| | 84 | |
| | 85 | return PrefixedPath{ |
| | 86 | .prefix = 0, |
| | 87 | .sub_path = resolved_path, |
| | 88 | }; |
| | 89 | } |
| | 90 | |
| 32 | /// This is 128 bits - Even with 2^54 cache entries, the probably of a collision would be under 10^-6 | 91 | /// This is 128 bits - Even with 2^54 cache entries, the probably of a collision would be under 10^-6 |
| 33 | pub const bin_digest_len = 16; | 92 | pub const bin_digest_len = 16; |
| 34 | pub const hex_digest_len = bin_digest_len * 2; | 93 | pub const hex_digest_len = bin_digest_len * 2; |
| ... | @@ -45,7 +104,7 @@ pub const Hasher = crypto.auth.siphash.SipHash128(1, 3); | ... | @@ -45,7 +104,7 @@ pub const Hasher = crypto.auth.siphash.SipHash128(1, 3); |
| 45 | pub const hasher_init: Hasher = Hasher.init(&[_]u8{0} ** Hasher.key_length); | 104 | pub const hasher_init: Hasher = Hasher.init(&[_]u8{0} ** Hasher.key_length); |
| 46 | | 105 | |
| 47 | pub const File = struct { | 106 | pub const File = struct { |
| 48 | path: ?[]const u8, | 107 | prefixed_path: ?PrefixedPath, |
| 49 | max_file_size: ?usize, | 108 | max_file_size: ?usize, |
| 50 | stat: Stat, | 109 | stat: Stat, |
| 51 | bin_digest: BinDigest, | 110 | bin_digest: BinDigest, |
| ... | @@ -57,13 +116,13 @@ pub const File = struct { | ... | @@ -57,13 +116,13 @@ pub const File = struct { |
| 57 | mtime: i128, | 116 | mtime: i128, |
| 58 | }; | 117 | }; |
| 59 | | 118 | |
| 60 | pub fn deinit(self: *File, allocator: Allocator) void { | 119 | pub fn deinit(self: *File, gpa: Allocator) void { |
| 61 | if (self.path) |owned_slice| { | 120 | if (self.prefixed_path) |pp| { |
| 62 | allocator.free(owned_slice); | 121 | gpa.free(pp.sub_path); |
| 63 | self.path = null; | 122 | self.prefixed_path = null; |
| 64 | } | 123 | } |
| 65 | if (self.contents) |contents| { | 124 | if (self.contents) |contents| { |
| 66 | allocator.free(contents); | 125 | gpa.free(contents); |
| 67 | self.contents = null; | 126 | self.contents = null; |
| 68 | } | 127 | } |
| 69 | self.* = undefined; | 128 | self.* = undefined; |
| ... | @@ -175,9 +234,6 @@ pub const Lock = struct { | ... | @@ -175,9 +234,6 @@ pub const Lock = struct { |
| 175 | } | 234 | } |
| 176 | }; | 235 | }; |
| 177 | | 236 | |
| 178 | /// Manifest manages project-local `zig-cache` directories. | | |
| 179 | /// This is not a general-purpose cache. | | |
| 180 | /// It is designed to be fast and simple, not to withstand attacks using specially-crafted input. | | |
| 181 | pub const Manifest = struct { | 237 | pub const Manifest = struct { |
| 182 | cache: *Cache, | 238 | cache: *Cache, |
| 183 | /// Current state for incremental hashing. | 239 | /// Current state for incremental hashing. |
| ... | @@ -220,21 +276,27 @@ pub const Manifest = struct { | ... | @@ -220,21 +276,27 @@ pub const Manifest = struct { |
| 220 | pub fn addFile(self: *Manifest, file_path: []const u8, max_file_size: ?usize) !usize { | 276 | pub fn addFile(self: *Manifest, file_path: []const u8, max_file_size: ?usize) !usize { |
| 221 | assert(self.manifest_file == null); | 277 | assert(self.manifest_file == null); |
| 222 | | 278 | |
| 223 | try self.files.ensureUnusedCapacity(self.cache.gpa, 1); | 279 | const gpa = self.cache.gpa; |
| 224 | const resolved_path = try fs.path.resolve(self.cache.gpa, &[_][]const u8{file_path}); | 280 | try self.files.ensureUnusedCapacity(gpa, 1); |
| | 281 | const prefixed_path = try self.cache.findPrefix(file_path); |
| | 282 | errdefer gpa.free(prefixed_path.sub_path); |
| | 283 | |
| | 284 | log.debug("Manifest.addFile {s} -> {d} {s}", .{ |
| | 285 | file_path, prefixed_path.prefix, prefixed_path.sub_path, |
| | 286 | }); |
| 225 | | 287 | |
| 226 | const idx = self.files.items.len; | | |
| 227 | self.files.addOneAssumeCapacity().* = .{ | 288 | self.files.addOneAssumeCapacity().* = .{ |
| 228 | .path = resolved_path, | 289 | .prefixed_path = prefixed_path, |
| 229 | .contents = null, | 290 | .contents = null, |
| 230 | .max_file_size = max_file_size, | 291 | .max_file_size = max_file_size, |
| 231 | .stat = undefined, | 292 | .stat = undefined, |
| 232 | .bin_digest = undefined, | 293 | .bin_digest = undefined, |
| 233 | }; | 294 | }; |
| 234 | | 295 | |
| 235 | self.hash.addBytes(resolved_path); | 296 | self.hash.add(prefixed_path.prefix); |
| | 297 | self.hash.addBytes(prefixed_path.sub_path); |
| 236 | | 298 | |
| 237 | return idx; | 299 | return self.files.items.len - 1; |
| 238 | } | 300 | } |
| 239 | | 301 | |
| 240 | pub fn hashCSource(self: *Manifest, c_source: Compilation.CSourceFile) !void { | 302 | pub fn hashCSource(self: *Manifest, c_source: Compilation.CSourceFile) !void { |
| ... | @@ -281,6 +343,7 @@ pub const Manifest = struct { | ... | @@ -281,6 +343,7 @@ pub const Manifest = struct { |
| 281 | /// option, one may call `toOwnedLock` to obtain a smaller object which can represent | 343 | /// option, one may call `toOwnedLock` to obtain a smaller object which can represent |
| 282 | /// the lock. `deinit` is safe to call whether or not `toOwnedLock` has been called. | 344 | /// the lock. `deinit` is safe to call whether or not `toOwnedLock` has been called. |
| 283 | pub fn hit(self: *Manifest) !bool { | 345 | pub fn hit(self: *Manifest) !bool { |
| | 346 | const gpa = self.cache.gpa; |
| 284 | assert(self.manifest_file == null); | 347 | assert(self.manifest_file == null); |
| 285 | | 348 | |
| 286 | self.failed_file_index = null; | 349 | self.failed_file_index = null; |
| ... | @@ -362,8 +425,8 @@ pub const Manifest = struct { | ... | @@ -362,8 +425,8 @@ pub const Manifest = struct { |
| 362 | | 425 | |
| 363 | self.want_refresh_timestamp = true; | 426 | self.want_refresh_timestamp = true; |
| 364 | | 427 | |
| 365 | const file_contents = try self.manifest_file.?.reader().readAllAlloc(self.cache.gpa, manifest_file_size_max); | 428 | const file_contents = try self.manifest_file.?.reader().readAllAlloc(gpa, manifest_file_size_max); |
| 366 | defer self.cache.gpa.free(file_contents); | 429 | defer gpa.free(file_contents); |
| 367 | | 430 | |
| 368 | const input_file_count = self.files.items.len; | 431 | const input_file_count = self.files.items.len; |
| 369 | var any_file_changed = false; | 432 | var any_file_changed = false; |
| ... | @@ -373,9 +436,9 @@ pub const Manifest = struct { | ... | @@ -373,9 +436,9 @@ pub const Manifest = struct { |
| 373 | defer idx += 1; | 436 | defer idx += 1; |
| 374 | | 437 | |
| 375 | const cache_hash_file = if (idx < input_file_count) &self.files.items[idx] else blk: { | 438 | const cache_hash_file = if (idx < input_file_count) &self.files.items[idx] else blk: { |
| 376 | const new = try self.files.addOne(self.cache.gpa); | 439 | const new = try self.files.addOne(gpa); |
| 377 | new.* = .{ | 440 | new.* = .{ |
| 378 | .path = null, | 441 | .prefixed_path = null, |
| 379 | .contents = null, | 442 | .contents = null, |
| 380 | .max_file_size = null, | 443 | .max_file_size = null, |
| 381 | .stat = undefined, | 444 | .stat = undefined, |
| ... | @@ -389,27 +452,35 @@ pub const Manifest = struct { | ... | @@ -389,27 +452,35 @@ pub const Manifest = struct { |
| 389 | const inode = iter.next() orelse return error.InvalidFormat; | 452 | const inode = iter.next() orelse return error.InvalidFormat; |
| 390 | const mtime_nsec_str = iter.next() orelse return error.InvalidFormat; | 453 | const mtime_nsec_str = iter.next() orelse return error.InvalidFormat; |
| 391 | const digest_str = iter.next() orelse return error.InvalidFormat; | 454 | const digest_str = iter.next() orelse return error.InvalidFormat; |
| | 455 | const prefix_str = iter.next() orelse return error.InvalidFormat; |
| 392 | const file_path = iter.rest(); | 456 | const file_path = iter.rest(); |
| 393 | | 457 | |
| 394 | cache_hash_file.stat.size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat; | 458 | cache_hash_file.stat.size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat; |
| 395 | cache_hash_file.stat.inode = fmt.parseInt(fs.File.INode, inode, 10) catch return error.InvalidFormat; | 459 | cache_hash_file.stat.inode = fmt.parseInt(fs.File.INode, inode, 10) catch return error.InvalidFormat; |
| 396 | cache_hash_file.stat.mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat; | 460 | cache_hash_file.stat.mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat; |
| 397 | _ = std.fmt.hexToBytes(&cache_hash_file.bin_digest, digest_str) catch return error.InvalidFormat; | 461 | _ = std.fmt.hexToBytes(&cache_hash_file.bin_digest, digest_str) catch return error.InvalidFormat; |
| | 462 | const prefix = fmt.parseInt(u8, prefix_str, 10) catch return error.InvalidFormat; |
| | 463 | if (prefix >= self.cache.prefixes_len) return error.InvalidFormat; |
| 398 | | 464 | |
| 399 | if (file_path.len == 0) { | 465 | if (file_path.len == 0) { |
| 400 | return error.InvalidFormat; | 466 | return error.InvalidFormat; |
| 401 | } | 467 | } |
| 402 | if (cache_hash_file.path) |p| { | 468 | if (cache_hash_file.prefixed_path) |pp| { |
| 403 | if (!mem.eql(u8, file_path, p)) { | 469 | if (pp.prefix != prefix or !mem.eql(u8, file_path, pp.sub_path)) { |
| 404 | return error.InvalidFormat; | 470 | return error.InvalidFormat; |
| 405 | } | 471 | } |
| 406 | } | 472 | } |
| 407 | | 473 | |
| 408 | if (cache_hash_file.path == null) { | 474 | if (cache_hash_file.prefixed_path == null) { |
| 409 | cache_hash_file.path = try self.cache.gpa.dupe(u8, file_path); | 475 | cache_hash_file.prefixed_path = .{ |
| | 476 | .prefix = prefix, |
| | 477 | .sub_path = try gpa.dupe(u8, file_path), |
| | 478 | }; |
| 410 | } | 479 | } |
| 411 | | 480 | |
| 412 | const this_file = fs.cwd().openFile(cache_hash_file.path.?, .{ .mode = .read_only }) catch |err| switch (err) { | 481 | const pp = cache_hash_file.prefixed_path.?; |
| | 482 | const dir = self.cache.prefixes()[pp.prefix].handle; |
| | 483 | const this_file = dir.openFile(pp.sub_path, .{ .mode = .read_only }) catch |err| switch (err) { |
| 413 | error.FileNotFound => { | 484 | error.FileNotFound => { |
| 414 | try self.upgradeToExclusiveLock(); | 485 | try self.upgradeToExclusiveLock(); |
| 415 | return false; | 486 | return false; |
| ... | @@ -535,8 +606,9 @@ pub const Manifest = struct { | ... | @@ -535,8 +606,9 @@ pub const Manifest = struct { |
| 535 | } | 606 | } |
| 536 | | 607 | |
| 537 | fn populateFileHash(self: *Manifest, ch_file: *File) !void { | 608 | fn populateFileHash(self: *Manifest, ch_file: *File) !void { |
| 538 | log.debug("populateFileHash {s}", .{ch_file.path.?}); | 609 | const pp = ch_file.prefixed_path.?; |
| 539 | const file = try fs.cwd().openFile(ch_file.path.?, .{}); | 610 | const dir = self.cache.prefixes()[pp.prefix].handle; |
| | 611 | const file = try dir.openFile(pp.sub_path, .{}); |
| 540 | defer file.close(); | 612 | defer file.close(); |
| 541 | | 613 | |
| 542 | const actual_stat = try file.stat(); | 614 | const actual_stat = try file.stat(); |
| ... | @@ -588,12 +660,17 @@ pub const Manifest = struct { | ... | @@ -588,12 +660,17 @@ pub const Manifest = struct { |
| 588 | pub fn addFilePostFetch(self: *Manifest, file_path: []const u8, max_file_size: usize) ![]const u8 { | 660 | pub fn addFilePostFetch(self: *Manifest, file_path: []const u8, max_file_size: usize) ![]const u8 { |
| 589 | assert(self.manifest_file != null); | 661 | assert(self.manifest_file != null); |
| 590 | | 662 | |
| 591 | const resolved_path = try fs.path.resolve(self.cache.gpa, &[_][]const u8{file_path}); | 663 | const gpa = self.cache.gpa; |
| 592 | errdefer self.cache.gpa.free(resolved_path); | 664 | const prefixed_path = try self.cache.findPrefix(file_path); |
| | 665 | errdefer gpa.free(prefixed_path.sub_path); |
| | 666 | |
| | 667 | log.debug("Manifest.addFilePostFetch {s} -> {d} {s}", .{ |
| | 668 | file_path, prefixed_path.prefix, prefixed_path.sub_path, |
| | 669 | }); |
| 593 | | 670 | |
| 594 | const new_ch_file = try self.files.addOne(self.cache.gpa); | 671 | const new_ch_file = try self.files.addOne(gpa); |
| 595 | new_ch_file.* = .{ | 672 | new_ch_file.* = .{ |
| 596 | .path = resolved_path, | 673 | .prefixed_path = prefixed_path, |
| 597 | .max_file_size = max_file_size, | 674 | .max_file_size = max_file_size, |
| 598 | .stat = undefined, | 675 | .stat = undefined, |
| 599 | .bin_digest = undefined, | 676 | .bin_digest = undefined, |
| ... | @@ -613,12 +690,17 @@ pub const Manifest = struct { | ... | @@ -613,12 +690,17 @@ pub const Manifest = struct { |
| 613 | pub fn addFilePost(self: *Manifest, file_path: []const u8) !void { | 690 | pub fn addFilePost(self: *Manifest, file_path: []const u8) !void { |
| 614 | assert(self.manifest_file != null); | 691 | assert(self.manifest_file != null); |
| 615 | | 692 | |
| 616 | const resolved_path = try fs.path.resolve(self.cache.gpa, &[_][]const u8{file_path}); | 693 | const gpa = self.cache.gpa; |
| 617 | errdefer self.cache.gpa.free(resolved_path); | 694 | const prefixed_path = try self.cache.findPrefix(file_path); |
| | 695 | errdefer gpa.free(prefixed_path.sub_path); |
| | 696 | |
| | 697 | log.debug("Manifest.addFilePost {s} -> {d} {s}", .{ |
| | 698 | file_path, prefixed_path.prefix, prefixed_path.sub_path, |
| | 699 | }); |
| 618 | | 700 | |
| 619 | const new_ch_file = try self.files.addOne(self.cache.gpa); | 701 | const new_ch_file = try self.files.addOne(gpa); |
| 620 | new_ch_file.* = .{ | 702 | new_ch_file.* = .{ |
| 621 | .path = resolved_path, | 703 | .prefixed_path = prefixed_path, |
| 622 | .max_file_size = null, | 704 | .max_file_size = null, |
| 623 | .stat = undefined, | 705 | .stat = undefined, |
| 624 | .bin_digest = undefined, | 706 | .bin_digest = undefined, |
| ... | @@ -633,17 +715,27 @@ pub const Manifest = struct { | ... | @@ -633,17 +715,27 @@ pub const Manifest = struct { |
| 633 | /// On success, cache takes ownership of `resolved_path`. | 715 | /// On success, cache takes ownership of `resolved_path`. |
| 634 | pub fn addFilePostContents( | 716 | pub fn addFilePostContents( |
| 635 | self: *Manifest, | 717 | self: *Manifest, |
| 636 | resolved_path: []const u8, | 718 | resolved_path: []u8, |
| 637 | bytes: []const u8, | 719 | bytes: []const u8, |
| 638 | stat: File.Stat, | 720 | stat: File.Stat, |
| 639 | ) error{OutOfMemory}!void { | 721 | ) error{OutOfMemory}!void { |
| 640 | assert(self.manifest_file != null); | 722 | assert(self.manifest_file != null); |
| | 723 | const gpa = self.cache.gpa; |
| 641 | | 724 | |
| 642 | const ch_file = try self.files.addOne(self.cache.gpa); | 725 | const ch_file = try self.files.addOne(gpa); |
| 643 | errdefer self.files.shrinkRetainingCapacity(self.files.items.len - 1); | 726 | errdefer self.files.shrinkRetainingCapacity(self.files.items.len - 1); |
| 644 | | 727 | |
| | 728 | log.debug("Manifest.addFilePostContents resolved_path={s}", .{resolved_path}); |
| | 729 | |
| | 730 | const prefixed_path = try self.cache.findPrefixResolved(resolved_path); |
| | 731 | errdefer gpa.free(prefixed_path.sub_path); |
| | 732 | |
| | 733 | log.debug("Manifest.addFilePostContents -> {d} {s}", .{ |
| | 734 | prefixed_path.prefix, prefixed_path.sub_path, |
| | 735 | }); |
| | 736 | |
| 645 | ch_file.* = .{ | 737 | ch_file.* = .{ |
| 646 | .path = resolved_path, | 738 | .prefixed_path = prefixed_path, |
| 647 | .max_file_size = null, | 739 | .max_file_size = null, |
| 648 | .stat = stat, | 740 | .stat = stat, |
| 649 | .bin_digest = undefined, | 741 | .bin_digest = undefined, |
| ... | @@ -742,12 +834,13 @@ pub const Manifest = struct { | ... | @@ -742,12 +834,13 @@ pub const Manifest = struct { |
| 742 | "{s}", | 834 | "{s}", |
| 743 | .{std.fmt.fmtSliceHexLower(&file.bin_digest)}, | 835 | .{std.fmt.fmtSliceHexLower(&file.bin_digest)}, |
| 744 | ) catch unreachable; | 836 | ) catch unreachable; |
| 745 | try writer.print("{d} {d} {d} {s} {s}\n", .{ | 837 | try writer.print("{d} {d} {d} {s} {d} {s}\n", .{ |
| 746 | file.stat.size, | 838 | file.stat.size, |
| 747 | file.stat.inode, | 839 | file.stat.inode, |
| 748 | file.stat.mtime, | 840 | file.stat.mtime, |
| 749 | &encoded_digest, | 841 | &encoded_digest, |
| 750 | file.path.?, | 842 | file.prefixed_path.?.prefix, |
| | 843 | file.prefixed_path.?.sub_path, |
| 751 | }); | 844 | }); |
| 752 | } | 845 | } |
| 753 | | 846 | |
| ... | @@ -889,6 +982,7 @@ test "cache file and then recall it" { | ... | @@ -889,6 +982,7 @@ test "cache file and then recall it" { |
| 889 | .gpa = testing.allocator, | 982 | .gpa = testing.allocator, |
| 890 | .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}), | 983 | .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}), |
| 891 | }; | 984 | }; |
| | 985 | cache.addPrefix(.{ .path = null, .handle = fs.cwd() }); |
| 892 | defer cache.manifest_dir.close(); | 986 | defer cache.manifest_dir.close(); |
| 893 | | 987 | |
| 894 | { | 988 | { |
| ... | @@ -960,6 +1054,7 @@ test "check that changing a file makes cache fail" { | ... | @@ -960,6 +1054,7 @@ test "check that changing a file makes cache fail" { |
| 960 | .gpa = testing.allocator, | 1054 | .gpa = testing.allocator, |
| 961 | .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}), | 1055 | .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}), |
| 962 | }; | 1056 | }; |
| | 1057 | cache.addPrefix(.{ .path = null, .handle = fs.cwd() }); |
| 963 | defer cache.manifest_dir.close(); | 1058 | defer cache.manifest_dir.close(); |
| 964 | | 1059 | |
| 965 | { | 1060 | { |
| ... | @@ -1022,6 +1117,7 @@ test "no file inputs" { | ... | @@ -1022,6 +1117,7 @@ test "no file inputs" { |
| 1022 | .gpa = testing.allocator, | 1117 | .gpa = testing.allocator, |
| 1023 | .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}), | 1118 | .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}), |
| 1024 | }; | 1119 | }; |
| | 1120 | cache.addPrefix(.{ .path = null, .handle = fs.cwd() }); |
| 1025 | defer cache.manifest_dir.close(); | 1121 | defer cache.manifest_dir.close(); |
| 1026 | | 1122 | |
| 1027 | { | 1123 | { |
| ... | @@ -1080,6 +1176,7 @@ test "Manifest with files added after initial hash work" { | ... | @@ -1080,6 +1176,7 @@ test "Manifest with files added after initial hash work" { |
| 1080 | .gpa = testing.allocator, | 1176 | .gpa = testing.allocator, |
| 1081 | .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}), | 1177 | .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}), |
| 1082 | }; | 1178 | }; |
| | 1179 | cache.addPrefix(.{ .path = null, .handle = fs.cwd() }); |
| 1083 | defer cache.manifest_dir.close(); | 1180 | defer cache.manifest_dir.close(); |
| 1084 | | 1181 | |
| 1085 | { | 1182 | { |