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 {...@@ -1032,24 +1032,32 @@ pub const Manifest = struct {
10321032
1033 /// Like `addFilePost` but when the file contents have already been loaded from disk.1033 /// Like `addFilePost` but when the file contents have already been loaded from disk.
1034 pub fn addFilePostContents(1034 pub fn addFilePostContents(
1035 self: *Manifest,1035 man: *Manifest,
1036 file_path: []const u8,1036 file_path: []const u8,
1037 bytes: []const u8,1037 bytes: []const u8,
1038 stat: File.Stat,1038 stat: File.Stat,
1039 ) !void {1039 ) !void {
1040 assert(self.manifest_file != null);1040 assert(man.manifest_file != null);
1041 const gpa = self.cache.gpa;1041 const gpa = man.cache.gpa;
10421042 const prefixed_path = try man.cache.findPrefix(file_path);
1043 const prefixed_path = try self.cache.findPrefix(file_path);1043 var keep = false;
1044 errdefer gpa.free(prefixed_path.sub_path);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{});1048 /// Low level function. `prefixed_path` references cloned memory. Returns
1047 errdefer _ = self.files.pop();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) {1060 if (gop.found_existing) return false;
1050 gpa.free(prefixed_path.sub_path);
1051 return;
1052 }
10531061
1054 const new_file = gop.key_ptr;1062 const new_file = gop.key_ptr;
10551063
...@@ -1062,7 +1070,7 @@ pub const Manifest = struct {...@@ -1062,7 +1070,7 @@ pub const Manifest = struct {
1062 .contents = null,1070 .contents = null,
1063 };1071 };
10641072
1065 if (try self.isProblematicTimestamp(new_file.stat.mtime)) {1073 if (try man.isProblematicTimestamp(new_file.stat.mtime)) {
1066 // The actual file has an unreliable timestamp, force it to be hashed1074 // The actual file has an unreliable timestamp, force it to be hashed
1067 new_file.stat.mtime = .zero;1075 new_file.stat.mtime = .zero;
1068 new_file.stat.inode = 0;1076 new_file.stat.inode = 0;
...@@ -1074,7 +1082,8 @@ pub const Manifest = struct {...@@ -1074,7 +1082,8 @@ pub const Manifest = struct {
1074 hasher.final(&new_file.bin_digest);1082 hasher.final(&new_file.bin_digest);
1075 }1083 }
10761084
1077 self.hash.hasher.update(&new_file.bin_digest);1085 man.hash.hasher.update(&new_file.bin_digest);
1086 return true;
1078 }1087 }
10791088
1080 pub fn addDepFilePost(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void {1089 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 {...@@ -506,10 +506,11 @@ pub const Path = struct {
506 // so that we prefer `.root = .local_cache` over `.root = .zig_lib`. The easiest way to do506 // so that we prefer `.root = .local_cache` over `.root = .zig_lib`. The easiest way to do
507 // this is simply to prioritize the longest root path.507 // this is simply to prioritize the longest root path.
508 const PathAndRoot = struct { ?[]const u8, Root };508 const PathAndRoot = struct { ?[]const u8, Root };
509 var roots: [3]PathAndRoot = .{509 var roots: [4]PathAndRoot = .{
510 .{ dirs.zig_lib.path, .zig_lib },510 .{ dirs.zig_lib.path, .zig_lib },
511 .{ dirs.global_cache.path, .global_cache },511 .{ dirs.global_cache.path, .global_cache },
512 .{ dirs.local_cache.path, .local_cache },512 .{ dirs.local_cache.path, .local_cache },
513 .{ dirs.build_root.path, .build_root },
513 };514 };
514 // This must be a stable sort, because the global and local cache directories may be the same, in515 // This must be a stable sort, because the global and local cache directories may be the same, in
515 // which case we need to make a consistent choice.516 // which case we need to make a consistent choice.
...@@ -660,7 +661,7 @@ pub const Path = struct {...@@ -660,7 +661,7 @@ pub const Path = struct {
660 /// This should not be used for most of the compiler pipeline, but is useful when emitting661 /// This should not be used for most of the compiler pipeline, but is useful when emitting
661 /// paths from the compilation (e.g. in debug info), because they will not depend on the cwd.662 /// paths from the compilation (e.g. in debug info), because they will not depend on the cwd.
662 /// The returned path is owned by the caller and allocated into `gpa`.663 /// 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 {
664 const root_path: []const u8 = switch (p.root) {665 const root_path: []const u8 = switch (p.root) {
665 .zig_lib => dirs.zig_lib.path orelse "",666 .zig_lib => dirs.zig_lib.path orelse "",
666 .global_cache => dirs.global_cache.path orelse "",667 .global_cache => dirs.global_cache.path orelse "",
...@@ -701,6 +702,66 @@ pub const Path = struct {...@@ -701,6 +702,66 @@ pub const Path = struct {
701 .no, .different_roots => false,702 .no, .different_roots => false,
702 };703 };
703 }704 }
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 }
704};765};
705766
706/// This small wrapper function just checks whether debug extensions are enabled before checking767/// 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...@@ -2776,7 +2837,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
27762837
2777 man = comp.cache_parent.obtain();2838 man = comp.cache_parent.obtain();
2778 whole.cache_manifest = &man;2839 whole.cache_manifest = &man;
2779 try addNonIncrementalStuffToCacheManifest(comp, arena, &man);2840 try addNonIncrementalStuffToCacheManifest(comp, &man);
27802841
2781 // Under `--time-report`, ignore cache hits; do the work anyway for those juicy numbers.2842 // Under `--time-report`, ignore cache hits; do the work anyway for those juicy numbers.
2782 const ignore_hit = comp.time_report != null;2843 const ignore_hit = comp.time_report != null;
...@@ -3328,15 +3389,14 @@ fn renameTmpIntoCache(...@@ -3328,15 +3389,14 @@ fn renameTmpIntoCache(
3328/// anything from the link cache manifest.3389/// anything from the link cache manifest.
3329pub const link_hash_implementation_version = 14;3390pub const link_hash_implementation_version = 14;
33303391
3331fn addNonIncrementalStuffToCacheManifest(3392fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifest) !void {
3332 comp: *Compilation,
3333 arena: Allocator,
3334 man: *Cache.Manifest,
3335) !void {
3336 comptime assert(link_hash_implementation_version == 14);3393 comptime assert(link_hash_implementation_version == 14);
33373394
3338 if (comp.zcu) |zcu| {3395 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
3341 // Synchronize with other matching comments: ZigOnlyHashStuff3401 // Synchronize with other matching comments: ZigOnlyHashStuff
3342 man.hash.addListOfBytes(comp.test_filters);3402 man.hash.addListOfBytes(comp.test_filters);
src/Zcu/PerThread.zig+4-10
...@@ -221,22 +221,19 @@ pub fn update(...@@ -221,22 +221,19 @@ pub fn update(
221 .astgen_failure, .success => {}, // the file was read successfully221 .astgen_failure, .success => {}, // the file was read successfully
222 }222 }
223223
224 const path = try file.path.toAbsolute(comp.dirs, gpa);
225 defer gpa.free(path);
226
227 const result = res: {224 const result = res: {
228 try whole.cache_manifest_mutex.lock(io);225 try whole.cache_manifest_mutex.lock(io);
229 defer whole.cache_manifest_mutex.unlock(io);226 defer whole.cache_manifest_mutex.unlock(io);
230 if (file.source) |source| {227 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);
232 } else {229 } else {
233 break :res man.addFilePost(path);230 break :res file.path.addToCacheManifestPostHit(man, &comp.dirs);
234 }231 }
235 };232 };
236 result catch |err| switch (err) {233 result catch |err| switch (err) {
237 error.OutOfMemory => |e| return e,234 error.OutOfMemory => |e| return e,
238 else => {235 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});
240 continue;237 continue;
241 },238 },
242 };239 };
...@@ -2965,13 +2962,10 @@ fn newEmbedFile(...@@ -2965,13 +2962,10 @@ fn newEmbedFile(
2965 const array_len = Value.fromInterned(new_file.val).typeOf(zcu).childType(zcu).arrayLen(zcu);2962 const array_len = Value.fromInterned(new_file.val).typeOf(zcu).childType(zcu).arrayLen(zcu);
2966 const contents = ip_str.toSlice(array_len, ip);2963 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
2971 try whole.cache_manifest_mutex.lock(io);2965 try whole.cache_manifest_mutex.lock(io);
2972 defer whole.cache_manifest_mutex.unlock(io);2966 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);
2975 }2969 }
29762970
2977 return new_file;2971 return new_file;
src/codegen/llvm.zig+1-1
...@@ -481,7 +481,7 @@ pub const Object = struct {...@@ -481,7 +481,7 @@ pub const Object = struct {
481 // way already, but here we throw all that sweet information481 // way already, but here we throw all that sweet information
482 // into the garbage can by converting into absolute paths. What482 // into the garbage can by converting into absolute paths. What
483 // a terrible tragedy.483 // 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
486 const debug_file = try builder.debugFile(486 const debug_file = try builder.debugFile(
487 try builder.metadataString(comp.root_name),487 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...@@ -4735,7 +4735,7 @@ fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (UpdateError || Writer.Err
4735 }4735 }
47364736
4737 for (dwarf.mods.keys(), dwarf.mods.values()) |mod, *mod_info| {4737 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);
4739 defer dwarf.gpa.free(root_dir_path);4739 defer dwarf.gpa.free(root_dir_path);
4740 mod_info.root_dir_path = try dwarf.debug_line_str.addString(dwarf, root_dir_path);4740 mod_info.root_dir_path = try dwarf.debug_line_str.addString(dwarf, root_dir_path);
4741 }4741 }