authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-17 18:03:23-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-19 12:39:36-07:00
loge8090258b5ca045d7b89fdf625115477514880e0
tree9256ae8ba431623b995ea8ba49d6973a24b43ebe
parente4b624eb7663ff3f3e10bff48024e7955d92b204

frontend: more resistant to absolute paths

When using Compilation.Path, we already know the path prefix, so just use that directly instead of taking a detour through absolute paths. Also, when using whole cache mode, don't call `addModuleTableToCacheHash` because it is redundant with the logic in `PerThread.update` which iterates over `zcu.alive_files` and adds those files discovered via `@import` to the whole cache manifest. That was causing files relative to cwd to be added to cache manifest rather than being relative to build_root.

5 files changed, 98 insertions(+), 35 deletions(-)

lib/std/Build/Cache.zig+23-14
......@@ -1032,24 +1032,32 @@ pub const Manifest = struct {
10321032
10331033 /// Like `addFilePost` but when the file contents have already been loaded from disk.
10341034 pub fn addFilePostContents(
1035 self: *Manifest,
1035 man: *Manifest,
10361036 file_path: []const u8,
10371037 bytes: []const u8,
10381038 stat: File.Stat,
10391039 ) !void {
1040 assert(self.manifest_file != null);
1041 const gpa = self.cache.gpa;
1042
1043 const prefixed_path = try self.cache.findPrefix(file_path);
1044 errdefer gpa.free(prefixed_path.sub_path);
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);
1046 }
10451047
1046 const gop = try self.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{});
1047 errdefer _ = self.files.pop();
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();
10481059
1049 if (gop.found_existing) {
1050 gpa.free(prefixed_path.sub_path);
1051 return;
1052 }
1060 if (gop.found_existing) return false;
10531061
10541062 const new_file = gop.key_ptr;
10551063
......@@ -1062,7 +1070,7 @@ pub const Manifest = struct {
10621070 .contents = null,
10631071 };
10641072
1065 if (try self.isProblematicTimestamp(new_file.stat.mtime)) {
1073 if (try man.isProblematicTimestamp(new_file.stat.mtime)) {
10661074 // The actual file has an unreliable timestamp, force it to be hashed
10671075 new_file.stat.mtime = .zero;
10681076 new_file.stat.inode = 0;
......@@ -1074,7 +1082,8 @@ pub const Manifest = struct {
10741082 hasher.final(&new_file.bin_digest);
10751083 }
10761084
1077 self.hash.hasher.update(&new_file.bin_digest);
1085 man.hash.hasher.update(&new_file.bin_digest);
1086 return true;
10781087 }
10791088
10801089 pub fn addDepFilePost(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void {
src/Compilation.zig+69-9
......@@ -506,10 +506,11 @@ pub const Path = struct {
506506 // so that we prefer `.root = .local_cache` over `.root = .zig_lib`. The easiest way to do
507507 // this is simply to prioritize the longest root path.
508508 const PathAndRoot = struct { ?[]const u8, Root };
509 var roots: [3]PathAndRoot = .{
509 var roots: [4]PathAndRoot = .{
510510 .{ dirs.zig_lib.path, .zig_lib },
511511 .{ dirs.global_cache.path, .global_cache },
512512 .{ dirs.local_cache.path, .local_cache },
513 .{ dirs.build_root.path, .build_root },
513514 };
514515 // This must be a stable sort, because the global and local cache directories may be the same, in
515516 // which case we need to make a consistent choice.
......@@ -660,7 +661,7 @@ pub const Path = struct {
660661 /// This should not be used for most of the compiler pipeline, but is useful when emitting
661662 /// paths from the compilation (e.g. in debug info), because they will not depend on the cwd.
662663 /// The returned path is owned by the caller and allocated into `gpa`.
663 pub fn toAbsolute(p: Path, dirs: std.zig.Directories, gpa: Allocator) Allocator.Error![]u8 {
664 pub fn toAbsolute(p: Path, dirs: *const std.zig.Directories, gpa: Allocator) Allocator.Error![]u8 {
664665 const root_path: []const u8 = switch (p.root) {
665666 .zig_lib => dirs.zig_lib.path orelse "",
666667 .global_cache => dirs.global_cache.path orelse "",
......@@ -701,6 +702,66 @@ pub const Path = struct {
701702 .no, .different_roots => false,
702703 };
703704 }
705
706 pub fn addToCacheManifestPostHit(p: Path, man: *Cache.Manifest, dirs: *const std.zig.Directories) !void {
707 comptime assert(0 == @backingInt(std.zig.Server.Message.PathPrefix.cwd));
708 comptime assert(1 == @backingInt(std.zig.Server.Message.PathPrefix.zig_lib));
709 comptime assert(2 == @backingInt(std.zig.Server.Message.PathPrefix.local_cache));
710 comptime assert(3 == @backingInt(std.zig.Server.Message.PathPrefix.global_cache));
711 comptime assert(4 == @backingInt(std.zig.Server.Message.PathPrefix.build_root));
712 comptime assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == 5);
713 const gpa = man.cache.gpa;
714 const prefixed_path: Cache.PrefixedPath = .{
715 .prefix = switch (p.root) {
716 .none => {
717 const path = try p.toAbsolute(dirs, gpa);
718 defer gpa.free(path);
719 return man.addFilePost(path);
720 },
721 .zig_lib => 1,
722 .local_cache => 2,
723 .global_cache => 3,
724 .build_root => 4,
725 },
726 .sub_path = try gpa.dupe(u8, p.sub_path),
727 };
728 var keep = false;
729 defer if (!keep) gpa.free(prefixed_path.sub_path);
730 keep = try man.addPrefixedPathPost(prefixed_path);
731 }
732
733 pub fn addToCacheManifestPostHitContents(
734 p: Path,
735 man: *Cache.Manifest,
736 dirs: *const std.zig.Directories,
737 bytes: []const u8,
738 stat: Cache.File.Stat,
739 ) !void {
740 comptime assert(0 == @backingInt(std.zig.Server.Message.PathPrefix.cwd));
741 comptime assert(1 == @backingInt(std.zig.Server.Message.PathPrefix.zig_lib));
742 comptime assert(2 == @backingInt(std.zig.Server.Message.PathPrefix.local_cache));
743 comptime assert(3 == @backingInt(std.zig.Server.Message.PathPrefix.global_cache));
744 comptime assert(4 == @backingInt(std.zig.Server.Message.PathPrefix.build_root));
745 comptime assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == 5);
746 const gpa = man.cache.gpa;
747 const prefixed_path: Cache.PrefixedPath = .{
748 .prefix = switch (p.root) {
749 .none => {
750 const path = try p.toAbsolute(dirs, gpa);
751 defer gpa.free(path);
752 return man.addFilePostContents(path, bytes, stat);
753 },
754 .zig_lib => 1,
755 .local_cache => 2,
756 .global_cache => 3,
757 .build_root => 4,
758 },
759 .sub_path = try gpa.dupe(u8, p.sub_path),
760 };
761 var keep = false;
762 defer if (!keep) gpa.free(prefixed_path.sub_path);
763 keep = try man.addPrefixedPathPostContents(prefixed_path, bytes, stat);
764 }
704765};
705766
706767/// This small wrapper function just checks whether debug extensions are enabled before checking
......@@ -2776,7 +2837,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
27762837
27772838 man = comp.cache_parent.obtain();
27782839 whole.cache_manifest = &man;
2779 try addNonIncrementalStuffToCacheManifest(comp, arena, &man);
2840 try addNonIncrementalStuffToCacheManifest(comp, &man);
27802841
27812842 // Under `--time-report`, ignore cache hits; do the work anyway for those juicy numbers.
27822843 const ignore_hit = comp.time_report != null;
......@@ -3328,15 +3389,14 @@ fn renameTmpIntoCache(
33283389/// anything from the link cache manifest.
33293390pub const link_hash_implementation_version = 14;
33303391
3331fn addNonIncrementalStuffToCacheManifest(
3332 comp: *Compilation,
3333 arena: Allocator,
3334 man: *Cache.Manifest,
3335) !void {
3392fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifest) !void {
33363393 comptime assert(link_hash_implementation_version == 14);
33373394
33383395 if (comp.zcu) |zcu| {
3339 try addModuleTableToCacheHash(zcu, arena, &man.hash, .{ .files = man });
3396 // No need to call `addModuleTableToCacheHash` here because it is
3397 // redundant with the logic in `PerThread.update` which iterates over
3398 // `zcu.alive_files` and adds those files discovered via `@import` to
3399 // the whole cache manifest.
33403400
33413401 // Synchronize with other matching comments: ZigOnlyHashStuff
33423402 man.hash.addListOfBytes(comp.test_filters);
src/Zcu/PerThread.zig+4-10
......@@ -221,22 +221,19 @@ pub fn update(
221221 .astgen_failure, .success => {}, // the file was read successfully
222222 }
223223
224 const path = try file.path.toAbsolute(comp.dirs, gpa);
225 defer gpa.free(path);
226
227224 const result = res: {
228225 try whole.cache_manifest_mutex.lock(io);
229226 defer whole.cache_manifest_mutex.unlock(io);
230227 if (file.source) |source| {
231 break :res man.addFilePostContents(path, source, file.stat);
228 break :res file.path.addToCacheManifestPostHitContents(man, &comp.dirs, source, file.stat);
232229 } else {
233 break :res man.addFilePost(path);
230 break :res file.path.addToCacheManifestPostHit(man, &comp.dirs);
234231 }
235232 };
236233 result catch |err| switch (err) {
237234 error.OutOfMemory => |e| return e,
238235 else => {
239 try pt.reportRetryableFileError(file_index, "unable to update cache: {s}", .{@errorName(err)});
236 try pt.reportRetryableFileError(file_index, "unable to update cache: {t}", .{err});
240237 continue;
241238 },
242239 };
......@@ -2965,13 +2962,10 @@ fn newEmbedFile(
29652962 const array_len = Value.fromInterned(new_file.val).typeOf(zcu).childType(zcu).arrayLen(zcu);
29662963 const contents = ip_str.toSlice(array_len, ip);
29672964
2968 const path_str = try path.toAbsolute(comp.dirs, gpa);
2969 defer gpa.free(path_str);
2970
29712965 try whole.cache_manifest_mutex.lock(io);
29722966 defer whole.cache_manifest_mutex.unlock(io);
29732967
2974 try man.addFilePostContents(path_str, contents, new_file.stat);
2968 try path.addToCacheManifestPostHitContents(man, &comp.dirs, contents, new_file.stat);
29752969 }
29762970
29772971 return new_file;
src/codegen/llvm.zig+1-1
......@@ -481,7 +481,7 @@ pub const Object = struct {
481481 // way already, but here we throw all that sweet information
482482 // into the garbage can by converting into absolute paths. What
483483 // a terrible tragedy.
484 const compile_unit_dir = try zcu.main_mod.root.toAbsolute(comp.dirs, arena);
484 const compile_unit_dir = try zcu.main_mod.root.toAbsolute(&comp.dirs, arena);
485485
486486 const debug_file = try builder.debugFile(
487487 try builder.metadataString(comp.root_name),
src/link/Dwarf.zig+1-1
......@@ -4735,7 +4735,7 @@ fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (UpdateError || Writer.Err
47354735 }
47364736
47374737 for (dwarf.mods.keys(), dwarf.mods.values()) |mod, *mod_info| {
4738 const root_dir_path = try mod.root.toAbsolute(zcu.comp.dirs, dwarf.gpa);
4738 const root_dir_path = try mod.root.toAbsolute(&zcu.comp.dirs, dwarf.gpa);
47394739 defer dwarf.gpa.free(root_dir_path);
47404740 mod_info.root_dir_path = try dwarf.debug_line_str.addString(dwarf, root_dir_path);
47414741 }