From 414641eb95cd006f4f291d259fe912885050bd2c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 14 Aug 2026 18:26:54 -0700 Subject: [PATCH 01/14] std.fs.path.resolvePosix: avoid deprecated managed array list API --- lib/std/fs/path.zig | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig index d5b7f751acf77f6a4b98951f8039bbbf4c20b752..55e362f456e88f6cc36715f04260e160a17a31cd 100644 --- a/lib/std/fs/path.zig +++ b/lib/std/fs/path.zig @@ -1093,23 +1093,23 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator return result.toOwnedSlice(allocator); } -/// This function is like a series of `cd` statements executed one after another. +/// Simulates a series of relative directory changes on a virtual filesystem +/// that has no symlinks. /// -/// It resolves "." and ".." to the best of its ability, but will not convert relative paths to -/// an absolute path, use Io.Dir.realpath instead. -/// -/// ".." components may persist in the resolved path if the resolved path is relative. +/// "." and ".." are resolved but will not make relative paths absolute. ".." +/// components remain in the resolved path when the resolved path is relative +/// and there are not previous components to cancel out. /// /// The result does not have a trailing path separator. /// /// This function does not perform any syscalls. Executing this series of path -/// lookups on the actual filesystem may produce different results due to +/// lookups on an actual filesystem may produce different results due to /// symlinks. -pub fn resolvePosix(allocator: Allocator, paths: []const []const u8) Allocator.Error![]u8 { +pub fn resolvePosix(gpa: Allocator, paths: []const []const u8) Allocator.Error![]u8 { assert(paths.len > 0); - var result = std.array_list.Managed(u8).init(allocator); - defer result.deinit(); + var result: std.ArrayList(u8) = .empty; + defer result.deinit(gpa); var negative_count: usize = 0; var is_abs = false; @@ -1135,23 +1135,23 @@ pub fn resolvePosix(allocator: Allocator, paths: []const []const u8) Allocator.E if (ends_with_slash or result.items.len == 0) break; } } else if (result.items.len > 0 or is_abs) { - try result.ensureUnusedCapacity(1 + component.len); + try result.ensureUnusedCapacity(gpa, 1 + component.len); result.appendAssumeCapacity('/'); result.appendSliceAssumeCapacity(component); } else { - try result.appendSlice(component); + try result.appendSlice(gpa, component); } } } if (result.items.len == 0) { if (is_abs) { - return allocator.dupe(u8, "/"); + return gpa.dupe(u8, "/"); } if (negative_count == 0) { - return allocator.dupe(u8, "."); + return gpa.dupe(u8, "."); } else { - const real_result = try allocator.alloc(u8, 3 * negative_count - 1); + const real_result = try gpa.alloc(u8, 3 * negative_count - 1); var count = negative_count - 1; var i: usize = 0; while (count > 0) : (count -= 1) { @@ -1164,9 +1164,9 @@ pub fn resolvePosix(allocator: Allocator, paths: []const []const u8) Allocator.E } if (negative_count == 0) { - return result.toOwnedSlice(); + return result.toOwnedSlice(gpa); } else { - const real_result = try allocator.alloc(u8, 3 * negative_count + result.items.len); + const real_result = try gpa.alloc(u8, 3 * negative_count + result.items.len); var count = negative_count; var i: usize = 0; while (count > 0) : (count -= 1) { -- 2.54.0 From 65b8b8b27bb4671443403e3d88534c0238b3c940 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 14 Aug 2026 18:27:09 -0700 Subject: [PATCH 02/14] std.Build.Cache: remove deprecated APIs improves Path hygiene in the compiler in some places also includes an assertion that will probably nede to be removed regarding absolute paths making it into the cache manifest --- lib/std/Build/Cache.zig | 31 +++------------------- src/Compilation.zig | 32 ++++++++++++----------- src/libs/freebsd.zig | 12 ++++++--- src/libs/glibc.zig | 12 ++++++--- src/libs/mingw.zig | 45 ++++++++++++-------------------- src/libs/netbsd.zig | 6 +++-- src/libs/openbsd.zig | 6 +++-- src/link.zig | 6 ++--- src/link/Lld.zig | 8 +++--- src/link/MachO.zig | 4 +-- src/link/MachO/CodeSignature.zig | 4 +-- src/main.zig | 6 ++--- 12 files changed, 75 insertions(+), 97 deletions(-) diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index a66f2bb543a162ba879174434ccc10dce52597de..908f531ec328839bdb3d1c3ea9a9e0ec05c45144 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -417,19 +417,8 @@ pub const Manifest = struct { return addFileInner(m, prefixed_path, handle, max_file_size); } - /// Deprecated; use `addFilePath`. - pub fn addFile(self: *Manifest, file_path: []const u8, max_file_size: ?usize) !usize { - assert(self.manifest_file == null); - - const gpa = self.cache.gpa; - try self.files.ensureUnusedCapacity(gpa, 1); - const prefixed_path = try self.cache.findPrefix(file_path); - errdefer gpa.free(prefixed_path.sub_path); - - return addFileInner(self, prefixed_path, null, max_file_size); - } - fn addFileInner(self: *Manifest, prefixed_path: PrefixedPath, handle: ?Io.File, max_file_size: ?usize) usize { + assert(!std.fs.path.isAbsolute(prefixed_path.sub_path)); const gop = self.files.getOrPutAssumeCapacityAdapted(prefixed_path, FilesAdapter{}); if (gop.found_existing) { self.cache.gpa.free(prefixed_path.sub_path); @@ -452,26 +441,12 @@ pub const Manifest = struct { return gop.index; } - /// Deprecated, use `addOptionalFilePath`. - pub fn addOptionalFile(self: *Manifest, optional_file_path: ?[]const u8) !void { - self.hash.add(optional_file_path != null); - const file_path = optional_file_path orelse return; - _ = try self.addFile(file_path, null); - } - pub fn addOptionalFilePath(self: *Manifest, optional_file_path: ?Path) !void { self.hash.add(optional_file_path != null); const file_path = optional_file_path orelse return; _ = try self.addFilePath(file_path, null); } - pub fn addListOfFiles(self: *Manifest, list_of_files: []const []const u8) !void { - self.hash.add(list_of_files.len); - for (list_of_files) |file_path| { - _ = try self.addFile(file_path, null); - } - } - pub fn addDepFile(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void { assert(self.manifest_file == null); return self.addDepFileMaybePost(dir, dep_file_sub_path); @@ -1127,13 +1102,13 @@ pub const Manifest = struct { // Clang is invoked in single-source mode but other programs may not .target, .target_must_resolve => {}, .prereq => |file_path| if (self.manifest_file == null) { - _ = try self.addFile(file_path, null); + _ = try self.addFilePath(.initCwd(file_path), null); } else try self.addFilePost(file_path), .prereq_must_resolve => { resolve_buf.clearRetainingCapacity(); try token.resolve(gpa, &resolve_buf); if (self.manifest_file == null) { - _ = try self.addFile(resolve_buf.items, null); + _ = try self.addFilePath(.initCwd(resolve_buf.items), null); } else try self.addFilePost(resolve_buf.items); }, else => |err| { diff --git a/src/Compilation.zig b/src/Compilation.zig index 3e8b2043a4d496800eacea61211e2a99eab37e11..b8ae83d05a5173b58ff700f04bae6a883725dc34 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1278,7 +1278,7 @@ pub const cache_helpers = struct { } pub fn hashCSource(self: *Cache.Manifest, c_source: CSourceFile) !void { - _ = try self.addFile(c_source.src_path, null); + _ = try self.addFilePath(.initCwd(c_source.src_path), null); // Hash the extra flags, with special care to call addFile for file parameters. // TODO this logic can likely be improved by utilizing clang_options_data.zig. const file_args = [_][]const u8{"-include"}; @@ -1289,7 +1289,7 @@ pub const cache_helpers = struct { for (file_args) |file_arg| { if (mem.eql(u8, file_arg, arg) and arg_i + 1 < c_source.extra_flags.len) { arg_i += 1; - _ = try self.addFile(c_source.extra_flags[arg_i], null); + _ = try self.addFilePath(.initCwd(c_source.extra_flags[arg_i]), null); } } } @@ -1466,8 +1466,8 @@ pub const CreateOptions = struct { stack_report: bool = false, link_eh_frame_hdr: bool = false, link_emit_relocs: bool = false, - linker_script: ?[]const u8 = null, - version_script: ?[]const u8 = null, + linker_script: ?Cache.Path = null, + version_script: ?Cache.Path = null, linker_allow_undefined_version: bool = false, linker_enable_new_dtags: ?bool = null, soname: ?[]const u8 = null, @@ -1546,7 +1546,7 @@ pub const CreateOptions = struct { /// (Darwin) Install name of the dylib install_name: ?[]const u8 = null, /// (Darwin) Path to entitlements file - entitlements: ?[]const u8 = null, + entitlements: ?Cache.Path = null, /// (Darwin) size of the __PAGEZERO segment pagezero_size: ?u64 = null, /// (Darwin) set minimum space for future expansion of the load commands @@ -2741,7 +2741,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE // If using the whole caching strategy, we check for *everything* up front, including // C source files. - log.debug("Compilation.update for {s}, CacheMode.{s}", .{ comp.root_name, @tagName(comp.cache_use) }); + log.debug("Compilation.update for {s}, CacheMode.{t}", .{ comp.root_name, comp.cache_use }); switch (comp.cache_use) { .none => |none| { assert(none.tmp_artifact_directory == null); @@ -2750,7 +2750,9 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path}); const handle = comp.dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{}) catch |err| { - return comp.setMiscFailure(.open_output, "failed to create output directory '{s}': {t}", .{ path, err }); + return comp.setMiscFailure(.open_output, "failed to create output directory {q}: {t}", .{ + path, err, + }); }; break :d .{ .path = path, .handle = handle }; }; @@ -3336,7 +3338,7 @@ fn addNonIncrementalStuffToCacheManifest( try link.hashInputs(man, comp.link_inputs); for (comp.c_objects.items) |c_object| { - _ = try man.addFile(c_object.src.src_path, null); + _ = try man.addFilePath(.initCwd(c_object.src.src_path), null); man.hash.addOptional(c_object.src.ext); man.hash.addListOfBytes(c_object.src.extra_flags); } @@ -3344,11 +3346,11 @@ fn addNonIncrementalStuffToCacheManifest( for (comp.win32_resources.items) |win32_resource| { switch (win32_resource.src) { .rc => |rc_src| { - _ = try man.addFile(rc_src.src_path, null); + _ = try man.addFilePath(.initCwd(rc_src.src_path), null); man.hash.addListOfBytes(rc_src.extra_flags); }, .manifest => |manifest_path| { - _ = try man.addFile(manifest_path, null); + _ = try man.addFilePath(.initCwd(manifest_path), null); }, } } @@ -3380,8 +3382,8 @@ fn addNonIncrementalStuffToCacheManifest( const opts = comp.cache_use.whole.lf_open_opts; - try man.addOptionalFile(opts.linker_script); - try man.addOptionalFile(opts.version_script); + try man.addOptionalFilePath(opts.linker_script); + try man.addOptionalFilePath(opts.version_script); man.hash.add(opts.allow_undefined_version); man.hash.addOptional(opts.enable_new_dtags); @@ -3440,7 +3442,7 @@ fn addNonIncrementalStuffToCacheManifest( // Mach-O specific stuff try link.File.MachO.hashAddFrameworks(man, opts.frameworks); - try man.addOptionalFile(opts.entitlements); + try man.addOptionalFilePath(opts.entitlements); man.hash.addOptional(opts.pagezero_size); man.hash.addOptional(opts.headerpad_size); man.hash.add(opts.headerpad_max_install_names); @@ -5818,7 +5820,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 // the XML data as a RT_MANIFEST resource. This means we can skip preprocessing, // include paths, CLI options, etc. if (win32_resource.src == .manifest) { - _ = try man.addFile(src_path, null); + _ = try man.addFilePath(.initCwd(src_path), null); const rc_basename = try std.fmt.allocPrint(arena, "{s}.rc", .{src_basename}); const res_basename = try std.fmt.allocPrint(arena, "{s}.res", .{src_basename}); @@ -5911,7 +5913,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 // We now know that we're compiling an .rc file const rc_src = win32_resource.src.rc; - _ = try man.addFile(rc_src.src_path, null); + _ = try man.addFilePath(.initCwd(rc_src.src_path), null); man.hash.addListOfBytes(rc_src.extra_flags); const rc_basename_noext = src_basename[0 .. src_basename.len - fs.path.extension(src_basename).len]; diff --git a/src/libs/freebsd.zig b/src/libs/freebsd.zig index 0895ef4280ed25592fbcfda9fa54960675d28271..6fb4b804525700fa7f4ce9a4f3c21363ae295fa9 100644 --- a/src/libs/freebsd.zig +++ b/src/libs/freebsd.zig @@ -458,8 +458,10 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye man.hash.add(target.abi); man.hash.add(target_os_version); - const full_abilists_path = try comp.dirs.zig_lib.join(arena, &.{abilists_path}); - const abilists_index = try man.addFile(full_abilists_path, abilists_max_size); + const abilists_index = try man.addFilePath(.{ + .root_dir = comp.dirs.zig_lib, + .sub_path = abilists_path, + }, abilists_max_size); if (try man.hit(prog_node)) { const digest = man.final(); @@ -1044,7 +1046,6 @@ fn buildSharedLib( const version: Version = .{ .major = sover, .minor = 0, .patch = 0 }; const ld_basename = path.basename(target.standardDynamicLinkerPath().get().?); const soname = if (mem.eql(u8, lib.name, "ld")) ld_basename else basename; - const map_file_path = try path.join(arena, &.{ bin_directory.path.?, all_map_basename }); const optimize_mode = comp.compilerRtOptMode(); const strip = comp.compilerRtStrip(); @@ -1113,7 +1114,10 @@ fn buildSharedLib( .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features, .clang_passthrough_mode = comp.clang_passthrough_mode, .version = version, - .version_script = map_file_path, + .version_script = .{ + .root_dir = bin_directory, + .sub_path = all_map_basename, + }, .soname = soname, .c_source_files = &c_source_files, .skip_linker_dependencies = true, diff --git a/src/libs/glibc.zig b/src/libs/glibc.zig index fce076dba86d74c52756d72419ed8859633992b5..7c20c33a0335fcab4b01e7da5c37c4d673e899b2 100644 --- a/src/libs/glibc.zig +++ b/src/libs/glibc.zig @@ -698,8 +698,10 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye man.hash.add(target.abi); man.hash.add(target_version); - const full_abilists_path = try comp.dirs.zig_lib.join(arena, &.{abilists_path}); - const abilists_index = try man.addFile(full_abilists_path, abilists_max_size); + const abilists_index = try man.addFilePath(.{ + .root_dir = comp.dirs.zig_lib, + .sub_path = abilists_path, + }, abilists_max_size); if (try man.hit(prog_node)) { const digest = man.final(); @@ -1188,7 +1190,6 @@ fn buildSharedLib( const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 }; const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?); const soname = if (mem.eql(u8, lib.name, "ld")) ld_basename else basename; - const map_file_path = try path.join(arena, &.{ bin_directory.path.?, all_map_basename }); const optimize_mode = comp.compilerRtOptMode(); const strip = comp.compilerRtStrip(); @@ -1257,7 +1258,10 @@ fn buildSharedLib( .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features, .clang_passthrough_mode = comp.clang_passthrough_mode, .version = version, - .version_script = map_file_path, + .version_script = .{ + .root_dir = bin_directory, + .sub_path = all_map_basename, + }, .soname = soname, .c_source_files = &c_source_files, .skip_linker_dependencies = true, diff --git a/src/libs/mingw.zig b/src/libs/mingw.zig index 224b5ec27b4c0e8dac994ec4a54f85a689a11449..73e741c4fa4e97fa2622ed9e13c43d59568bd89c 100644 --- a/src/libs/mingw.zig +++ b/src/libs/mingw.zig @@ -243,7 +243,10 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8, prog_node: std.P var man = cache.obtain(); defer man.deinit(); - _ = try man.addFile(def_file_path, null); + _ = try man.addFilePath(.{ + .root_dir = comp.dirs.zig_lib, + .sub_path = def_file_path, + }, null); const final_lib_basename = try std.fmt.allocPrint(gpa, "{s}.lib", .{lib_name}); errdefer gpa.free(final_lib_basename); @@ -384,7 +387,7 @@ pub fn libExists( /// This function body is verbose but all it does is test 3 different paths and /// see if a .def file exists. fn findDef( - allocator: Allocator, + gpa: Allocator, io: Io, target: *const std.Target, zig_lib_directory: Cache.Directory, @@ -398,21 +401,17 @@ fn findDef( else => unreachable, }; - var override_path = std.array_list.Managed(u8).init(allocator); - defer override_path.deinit(); + var override_path: std.ArrayList(u8) = .empty; + defer override_path.deinit(gpa); const s = path.sep_str; { // Try the archtecture-specific path first. - const fmt_path = "libc" ++ s ++ "mingw" ++ s ++ "{s}" ++ s ++ "{s}.def"; - if (zig_lib_directory.path) |p| { - try override_path.print("{s}" ++ s ++ fmt_path, .{ p, lib_path, lib_name }); - } else { - try override_path.print(fmt_path, .{ lib_path, lib_name }); - } - if (Io.Dir.cwd().access(io, override_path.items, .{})) |_| { - return override_path.toOwnedSlice(); + override_path.shrinkRetainingCapacity(0); + try override_path.print(gpa, "libc" ++ s ++ "mingw" ++ s ++ "{s}" ++ s ++ "{s}.def", .{ lib_path, lib_name }); + if (zig_lib_directory.handle.access(io, override_path.items, .{})) |_| { + return override_path.toOwnedSlice(gpa); } else |err| switch (err) { error.FileNotFound => {}, else => |e| return e, @@ -422,14 +421,9 @@ fn findDef( { // Try the generic version. override_path.shrinkRetainingCapacity(0); - const fmt_path = "libc" ++ s ++ "mingw" ++ s ++ "lib-common" ++ s ++ "{s}.def"; - if (zig_lib_directory.path) |p| { - try override_path.print("{s}" ++ s ++ fmt_path, .{ p, lib_name }); - } else { - try override_path.print(fmt_path, .{lib_name}); - } - if (Io.Dir.cwd().access(io, override_path.items, .{})) |_| { - return override_path.toOwnedSlice(); + try override_path.print(gpa, "libc" ++ s ++ "mingw" ++ s ++ "lib-common" ++ s ++ "{s}.def", .{lib_name}); + if (zig_lib_directory.handle.access(io, override_path.items, .{})) |_| { + return override_path.toOwnedSlice(gpa); } else |err| switch (err) { error.FileNotFound => {}, else => |e| return e, @@ -439,14 +433,9 @@ fn findDef( { // Try the generic version and preprocess it. override_path.shrinkRetainingCapacity(0); - const fmt_path = "libc" ++ s ++ "mingw" ++ s ++ "lib-common" ++ s ++ "{s}.def.in"; - if (zig_lib_directory.path) |p| { - try override_path.print("{s}" ++ s ++ fmt_path, .{ p, lib_name }); - } else { - try override_path.print(fmt_path, .{lib_name}); - } - if (Io.Dir.cwd().access(io, override_path.items, .{})) |_| { - return override_path.toOwnedSlice(); + try override_path.print(gpa, "libc" ++ s ++ "mingw" ++ s ++ "lib-common" ++ s ++ "{s}.def.in", .{lib_name}); + if (zig_lib_directory.handle.access(io, override_path.items, .{})) |_| { + return override_path.toOwnedSlice(gpa); } else |err| switch (err) { error.FileNotFound => {}, else => |e| return e, diff --git a/src/libs/netbsd.zig b/src/libs/netbsd.zig index fb811df536ed7fa55f2ec630534e1c995ef978f7..3d7c94ce4974bb8f9fb926cad43ca99c1919cfc7 100644 --- a/src/libs/netbsd.zig +++ b/src/libs/netbsd.zig @@ -406,8 +406,10 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye man.hash.add(target.abi); man.hash.add(target_version); - const full_abilists_path = try comp.dirs.zig_lib.join(arena, &.{abilists_path}); - const abilists_index = try man.addFile(full_abilists_path, abilists_max_size); + const abilists_index = try man.addFilePath(.{ + .root_dir = comp.dirs.zig_lib, + .sub_path = abilists_path, + }, abilists_max_size); if (try man.hit(prog_node)) { const digest = man.final(); diff --git a/src/libs/openbsd.zig b/src/libs/openbsd.zig index e38183ab75db6a29eae22695f52550f69b35858c..2e0159b677de2ff86d57ac27e130c63f341e898f 100644 --- a/src/libs/openbsd.zig +++ b/src/libs/openbsd.zig @@ -327,8 +327,10 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye man.hash.add(target.abi); man.hash.add(target_version); - const full_abilists_path = try comp.dirs.zig_lib.join(arena, &.{abilists_path}); - const abilists_index = try man.addFile(full_abilists_path, abilists_max_size); + const abilists_index = try man.addFilePath(.{ + .root_dir = comp.dirs.zig_lib, + .sub_path = abilists_path, + }, abilists_max_size); if (try man.hit(prog_node)) { const digest = man.final(); diff --git a/src/link.zig b/src/link.zig index 836621f3fea4a25a1f37d5d313d942f8229fcfad..eeab7e4f56f441070fc8817c7d8969c0558a4ee0 100644 --- a/src/link.zig +++ b/src/link.zig @@ -461,8 +461,8 @@ pub const File = struct { allow_undefined_version: bool, enable_new_dtags: ?bool, subsystem: ?std.zig.Subsystem, - linker_script: ?[]const u8, - version_script: ?[]const u8, + linker_script: ?Path, + version_script: ?Path, soname: ?[]const u8, print_gc_sections: bool, print_icf_sections: bool, @@ -493,7 +493,7 @@ pub const File = struct { /// Install name for the dylib install_name: ?[]const u8, /// Path to entitlements file - entitlements: ?[]const u8, + entitlements: ?Path, /// size of the __PAGEZERO segment pagezero_size: ?u64, /// Set minimum space for future expansion of the load commands diff --git a/src/link/Lld.zig b/src/link/Lld.zig index b1ac4a2834bf7ba5992d658304e40e0ccf2a9a18..90bf4e3966a0dc6400df2c3d56db63d287f19aa3 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -75,8 +75,8 @@ pub const Elf = struct { entry_name: ?[]const u8, hash_style: HashStyle, image_base: u64, - linker_script: ?[]const u8, - version_script: ?[]const u8, + linker_script: ?Cache.Path, + version_script: ?Cache.Path, sort_section: ?SortSection, print_icf_sections: bool, print_map: bool, @@ -930,7 +930,7 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { if (elf.linker_script) |linker_script| { try argv.append("-T"); - try argv.append(linker_script); + try argv.append(try linker_script.toString(arena)); } if (elf.sort_section) |how| { @@ -1086,7 +1086,7 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { } if (elf.version_script) |version_script| { try argv.append("-version-script"); - try argv.append(version_script); + try argv.append(try version_script.toString(arena)); } if (elf.allow_undefined_version) { try argv.append("--undefined-version"); diff --git a/src/link/MachO.zig b/src/link/MachO.zig index c03d4eac74b73b293e66bf08258a69fea466b721..0a2c0c9c50c14065f77e166fac72b68ef6820bb0 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -127,7 +127,7 @@ frameworks: []const Framework, /// TODO: unify with soname install_name: ?[]const u8, /// Path to entitlements file. -entitlements: ?[]const u8, +entitlements: ?Path, compatibility_version: ?std.SemanticVersion, /// Entry name entry_name: ?[]const u8, @@ -580,7 +580,7 @@ pub fn flush( var codesig = CodeSignature.init(self.getPageSize()); codesig.code_directory.ident = fs.path.basename(self.base.emit.sub_path); if (self.entitlements) |path| codesig.addEntitlements(gpa, io, path) catch |err| - return diags.fail("failed to add entitlements from {s}: {t}", .{ path, err }); + return diags.fail("failed to add entitlements from {f}: {t}", .{ path, err }); try self.writeCodeSignaturePadding(&codesig); break :blk codesig; } else null; diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index 88fae25e73f87e944e4dcf0b9cb522bee6db4936..8cad16f12990538304dfc084515afe8174c18aea 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -246,8 +246,8 @@ pub fn deinit(self: *CodeSignature, allocator: Allocator) void { } } -pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, io: Io, path: []const u8) !void { - const inner = try Io.Dir.cwd().readFileAlloc(io, path, allocator, .limited(std.math.maxInt(u32))); +pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, io: Io, path: std.Build.Cache.Path) !void { + const inner = try path.root_dir.handle.readFileAlloc(io, path.sub_path, allocator, .limited(std.math.maxInt(u32))); self.entitlements = .{ .inner = inner }; } diff --git a/src/main.zig b/src/main.zig index 733620bd34eb552cb1784a2f36bfb70cab1f864c..e25a96714ae8e903fdfd016af0fb3be7d30bda7f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3666,8 +3666,8 @@ fn buildOutputType( .want_compiler_rt = if (zig_cc_explicitly_link_compiler_rt) true else want_compiler_rt, .want_ubsan_rt = want_ubsan_rt, .hash_style = hash_style, - .linker_script = linker_script, - .version_script = version_script, + .linker_script = if (linker_script) |p| .initCwd(p) else null, + .version_script = if (version_script) |p| .initCwd(p) else null, .linker_allow_undefined_version = linker_allow_undefined_version, .linker_enable_new_dtags = linker_enable_new_dtags, .disable_c_depfile = disable_c_depfile, @@ -3740,7 +3740,7 @@ fn buildOutputType( .debug_incremental = debug_incremental, .enable_link_snapshots = enable_link_snapshots, .install_name = install_name, - .entitlements = entitlements, + .entitlements = if (entitlements) |p| .initCwd(p) else null, .pagezero_size = pagezero_size, .headerpad_size = headerpad_size, .headerpad_max_install_names = headerpad_max_install_names, -- 2.54.0 From 9df1d1e5ed8d8f98f4794087e5dee5d2a1b0605e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 14 Aug 2026 18:47:34 -0700 Subject: [PATCH 03/14] std.Build.Cache: add some more doomed assertions --- lib/std/Build/Cache.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index 908f531ec328839bdb3d1c3ea9a9e0ec05c45144..64331f583515b109e2e5cd5fe00b31c43c974e09 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -951,6 +951,7 @@ pub const Manifest = struct { /// will need to be recompiled if the imported file is changed. pub fn addFilePostFetch(self: *Manifest, file_path: []const u8, max_file_size: usize) ![]const u8 { assert(self.manifest_file != null); + assert(!std.fs.path.isAbsolute(file_path)); const gpa = self.cache.gpa; const prefixed_path = try self.cache.findPrefix(file_path); @@ -1008,6 +1009,7 @@ pub const Manifest = struct { /// whether or not `prefixed_path.sub_path` should be kept. pub fn addPrefixedPathPost(man: *Manifest, prefixed_path: PrefixedPath) !bool { assert(man.manifest_file != null); + assert(!std.fs.path.isAbsolute(prefixed_path.sub_path)); const gpa = man.cache.gpa; const gop = try man.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{}); @@ -1039,6 +1041,7 @@ pub const Manifest = struct { stat: File.Stat, ) !void { assert(self.manifest_file != null); + assert(!std.fs.path.isAbsolute(file_path)); const gpa = self.cache.gpa; const prefixed_path = try self.cache.findPrefix(file_path); -- 2.54.0 From 0c4dcb12b93d88028c032d76241aac83b0958efd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 14 Aug 2026 19:36:29 -0700 Subject: [PATCH 04/14] CLI: move help for --dep to correct section --- src/main.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.zig b/src/main.zig index e25a96714ae8e903fdfd016af0fb3be7d30bda7f..d2068b8cdbb6a53f63dd28ac667503f7495e777e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -568,13 +568,12 @@ const usage_build_generic = \\ \\Global Compile Options: \\ --name [name] Compilation unit name (not a file path) - \\ --libc [file] Provide a file which specifies libc paths - \\ -x language Treat subsequent input files as having type - \\ --dep [[import=]name] Add an entry to the next module's import table \\ -M[name][=src] Create a module based on the current per-module settings. \\ The first module is the main module. \\ "std" can be configured by omitting src \\ After a -M argument, per-module settings are reset. + \\ --libc [file] Provide a file which specifies libc paths + \\ -x [language] Treat subsequent input files as having type \\ --error-limit [num] Set the maximum amount of distinct error values \\ -fllvm Force using LLVM as the codegen backend \\ -fno-llvm Prevent using LLVM as the codegen backend @@ -599,6 +598,7 @@ const usage_build_generic = \\ --time-report Send timing diagnostics to '--listen' clients \\ \\Per-Module Compile Options: + \\ --dep [[import=]name] Add an entry to the next module's import table \\ -target [name] -- see the targets command \\ -O [mode] Choose what to optimize for \\ debug (default) Prioritize bug detection, accurate debug info, compilation speed -- 2.54.0 From 83d41cf04dcec3d66a84b6c2a727621c076e971e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 14 Aug 2026 21:12:32 -0700 Subject: [PATCH 05/14] add build_root to cache prefix directories --- lib/compiler/Maker.zig | 2 + lib/compiler/Maker/Step.zig | 7 +++ lib/compiler/Maker/Step/Compile.zig | 3 +- lib/std/Build/Cache.zig | 8 +-- lib/std/zig.zig | 47 ++++++++++----- lib/std/zig/Server.zig | 1 + src/Compilation.zig | 33 +++++++---- src/Zcu/PerThread.zig | 2 +- src/codegen/llvm.zig | 1 + src/main.zig | 92 ++++++++++++++--------------- 10 files changed, 117 insertions(+), 79 deletions(-) diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig index 92db208cbc7e299ee0282a504ca1dd372f83a604..0a0b68aabbe65d31f6f24518c63aa40e9bf387ee 100644 --- a/lib/compiler/Maker.zig +++ b/lib/compiler/Maker.zig @@ -614,6 +614,8 @@ pub fn main(init: process.Init.Minimal) !void { comptime assert(1 == @backingInt(std.zig.Server.Message.PathPrefix.zig_lib)); comptime assert(2 == @backingInt(std.zig.Server.Message.PathPrefix.local_cache)); comptime assert(3 == @backingInt(std.zig.Server.Message.PathPrefix.global_cache)); + comptime assert(4 == @backingInt(std.zig.Server.Message.PathPrefix.build_root)); + comptime assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == 5); graph.cache.hash.addBytes(builtin.zig_version_string); diff --git a/lib/compiler/Maker/Step.zig b/lib/compiler/Maker/Step.zig index 2ce90b5a9b7725d452258af167de82b60b973a6b..0fd82226cce06989c0acea46f4f22e4dcd6d6e58 100644 --- a/lib/compiler/Maker/Step.zig +++ b/lib/compiler/Maker/Step.zig @@ -656,6 +656,13 @@ fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *Zi }; try addWatchInputFromPath(s, maker, path, Dir.path.basename(sub_path)); }, + .build_root => { + const path: Path = .{ + .root_dir = graph.build_root_directory, + .sub_path = sub_path_dirname, + }; + try addWatchInputFromPath(s, maker, path, Dir.path.basename(sub_path)); + }, } } }, diff --git a/lib/compiler/Maker/Step/Compile.zig b/lib/compiler/Maker/Step/Compile.zig index 1e60ad77a30a78bf0f903527430ac76b7af4fdd4..bca0df4de84cc95c42b2067d3ec1f5d65176488b 100644 --- a/lib/compiler/Maker/Step/Compile.zig +++ b/lib/compiler/Maker/Step/Compile.zig @@ -658,9 +658,10 @@ fn lowerZigArgs( zig_args.appendAssumeCapacity(libc_file); } - (try zig_args.addManyAsArray(gpa, 4)).* = .{ + (try zig_args.addManyAsArray(gpa, 6)).* = .{ "--cache-dir", graph.local_cache_root.path orelse ".", "--global-cache-dir", graph.global_cache_root.path orelse ".", + "--build-root", graph.build_root_directory.path orelse ".", }; try zig_args.ensureUnusedCapacity(gpa, 1); diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index 64331f583515b109e2e5cd5fe00b31c43c974e09..2391e698c4158197c63db208cceb0618b342a208 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -418,7 +418,6 @@ pub const Manifest = struct { } fn addFileInner(self: *Manifest, prefixed_path: PrefixedPath, handle: ?Io.File, max_file_size: ?usize) usize { - assert(!std.fs.path.isAbsolute(prefixed_path.sub_path)); const gop = self.files.getOrPutAssumeCapacityAdapted(prefixed_path, FilesAdapter{}); if (gop.found_existing) { self.cache.gpa.free(prefixed_path.sub_path); @@ -951,7 +950,6 @@ pub const Manifest = struct { /// will need to be recompiled if the imported file is changed. pub fn addFilePostFetch(self: *Manifest, file_path: []const u8, max_file_size: usize) ![]const u8 { assert(self.manifest_file != null); - assert(!std.fs.path.isAbsolute(file_path)); const gpa = self.cache.gpa; const prefixed_path = try self.cache.findPrefix(file_path); @@ -1009,7 +1007,6 @@ pub const Manifest = struct { /// whether or not `prefixed_path.sub_path` should be kept. pub fn addPrefixedPathPost(man: *Manifest, prefixed_path: PrefixedPath) !bool { assert(man.manifest_file != null); - assert(!std.fs.path.isAbsolute(prefixed_path.sub_path)); const gpa = man.cache.gpa; const gop = try man.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{}); @@ -1041,7 +1038,6 @@ pub const Manifest = struct { stat: File.Stat, ) !void { assert(self.manifest_file != null); - assert(!std.fs.path.isAbsolute(file_path)); const gpa = self.cache.gpa; const prefixed_path = try self.cache.findPrefix(file_path); @@ -1268,10 +1264,10 @@ pub const Manifest = struct { } } - pub fn populateOtherManifest(man: *Manifest, other: *Manifest, prefix_map: [4]u8) Allocator.Error!void { + pub fn populateOtherManifest(man: *Manifest, other: *Manifest, prefix_map: [5]u8) Allocator.Error!void { const gpa = other.cache.gpa; assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == man.cache.prefixes_len); - assert(man.cache.prefixes_len == 4); + assert(man.cache.prefixes_len == 5); for (man.files.keys()) |file| { const prefixed_path: PrefixedPath = .{ .prefix = prefix_map[file.prefixed_path.prefix], diff --git a/lib/std/zig.zig b/lib/std/zig.zig index ec6fc7d630a92133751c8ae481ff193d3fc0dfff..4853d43f93a2814160ba278fa04fa6ef2d011102 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -1283,6 +1283,10 @@ pub const Directories = struct { /// `local_cache.path` is resolved (`resolvePath`) or `null` for cwd. /// This may be the same as `global_cache`. local_cache: Cache.Directory, + /// The directory that contains build.zig. This path is provided by the + /// build system, when the build system is used, otherwise, it is `null` + /// for cwd. + build_root: Cache.Directory, pub fn deinit(dirs: *Directories, io: Io) void { // The local and global caches could be the same. @@ -1291,6 +1295,7 @@ pub const Directories = struct { dirs.global_cache.handle.close(io); if (close_local) dirs.local_cache.handle.close(io); dirs.zig_lib.handle.close(io); + if (dirs.build_root.path != null) dirs.build_root.handle.close(io); } /// Returns a `Directories` where `local_cache` is replaced with `global_cache`, intended for @@ -1302,6 +1307,7 @@ pub const Directories = struct { .zig_lib = dirs.zig_lib, .global_cache = dirs.global_cache, .local_cache = dirs.global_cache, + .build_root = dirs.build_root, }; } @@ -1311,12 +1317,10 @@ pub const Directories = struct { global, }; - /// Uses `std.process.fatal` on error conditions. - pub fn init( - arena: Allocator, - io: Io, + pub const InitOptions = struct { override_zig_lib: ?[]const u8, override_global_cache: ?[]const u8, + build_root: ?[]const u8, local_cache_strat: LocalCacheStrategy, preopens: std.process.Preopens, self_exe_path: switch (builtin.target.os.tag) { @@ -1325,27 +1329,38 @@ pub const Directories = struct { }, environ_map: *const std.process.Environ.Map, cwd: []const u8, - ) Directories { + }; + + /// Uses `std.process.fatal` on error conditions. + pub fn init(arena: Allocator, io: Io, options: InitOptions) Directories { const wasi = builtin.target.os.tag == .wasi; + const cwd = options.cwd; const zig_lib: Cache.Directory = d: { - if (override_zig_lib) |path| break :d openUnresolved(arena, io, cwd, path, .@"zig lib"); - if (wasi) break :d getPreopen(preopens, "/lib"); - break :d findZigLibDirFromSelfExe(arena, io, cwd, self_exe_path) catch |err| { - fatal("unable to find zig installation directory from executable path {q}: {t}", .{ self_exe_path, err }); + if (options.override_zig_lib) |path| break :d openUnresolved(arena, io, cwd, path, .@"zig lib"); + if (wasi) break :d getPreopen(options.preopens, "/lib"); + break :d findZigLibDirFromSelfExe(arena, io, cwd, options.self_exe_path) catch |err| { + fatal("unable to find zig installation directory from executable path {q}: {t}", .{ + options.self_exe_path, err, + }); }; }; + const build_root: Cache.Directory = if (options.build_root) |s| + // TODO this ends up setting path to null, leaking the fd in deinit + openUnresolved(arena, io, cwd, s, .@"build root") + else + .cwd(); const global_cache: Cache.Directory = d: { - if (override_global_cache) |path| break :d openUnresolved(arena, io, cwd, path, .@"global cache"); - if (wasi) break :d getPreopen(preopens, "/cache"); - const path = resolveGlobalCacheDir(arena, environ_map) catch |err| { + if (options.override_global_cache) |path| break :d openUnresolved(arena, io, cwd, path, .@"global cache"); + if (wasi) break :d getPreopen(options.preopens, "/cache"); + const path = resolveGlobalCacheDir(arena, options.environ_map) catch |err| { fatal("unable to resolve zig cache directory: {t}", .{err}); }; break :d openUnresolved(arena, io, cwd, path, .@"global cache"); }; - const local_cache = getLocalCacheDirectory(arena, io, cwd, global_cache, local_cache_strat); + const local_cache = getLocalCacheDirectory(arena, io, cwd, global_cache, options.local_cache_strat); if (mem.eql(u8, zig_lib.path orelse "", global_cache.path orelse "")) { fatal("zig lib directory '{f}' cannot be equal to global cache directory '{f}'", .{ zig_lib, global_cache }); @@ -1359,6 +1374,7 @@ pub const Directories = struct { .zig_lib = zig_lib, .global_cache = global_cache, .local_cache = local_cache, + .build_root = build_root, }; } @@ -1395,14 +1411,14 @@ pub const Directories = struct { io: Io, cwd: []const u8, unresolved_path: []const u8, - thing: enum { @"zig lib", @"global cache", @"local cache" }, + thing: enum { @"zig lib", @"global cache", @"local cache", @"build root" }, ) Cache.Directory { const path = resolvePath(arena, cwd, &.{unresolved_path}) catch |err| { fatal("unable to resolve {t} directory: {t}", .{ thing, err }); }; const nonempty_path = if (path.len == 0) "." else path; const handle_or_err = switch (thing) { - .@"zig lib" => Dir.cwd().openDir(io, nonempty_path, .{}), + .@"zig lib", .@"build root" => Dir.cwd().openDir(io, nonempty_path, .{}), .@"global cache", .@"local cache" => Dir.cwd().createDirPathOpen(io, nonempty_path, .{}), }; return .{ @@ -1747,6 +1763,7 @@ pub fn buildExeSubprocess( const sub_path = try gpa.dupe(u8, prefixed_path[1..]); var keep = false; defer if (!keep) gpa.free(sub_path); + log.debug("file system input: {t} {s}", .{ prefix, sub_path }); keep = man.addPrefixedPathPost(.{ .prefix = @backingInt(prefix), .sub_path = sub_path, diff --git a/lib/std/zig/Server.zig b/lib/std/zig/Server.zig index 1f6d208084abdcc9d93ef33929e36aa0239549af..eac3555acf7074a174f333401e0f79c70c23ab7e 100644 --- a/lib/std/zig/Server.zig +++ b/lib/std/zig/Server.zig @@ -135,6 +135,7 @@ pub const Message = struct { zig_lib, local_cache, global_cache, + build_root, }; /// Trailing: diff --git a/src/Compilation.zig b/src/Compilation.zig index b8ae83d05a5173b58ff700f04bae6a883725dc34..92d77b97f882816fae92bf88c8db3d18b1e37ec5 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -397,6 +397,7 @@ pub const Path = struct { global_cache, /// `sub_path` is relative to the local cache directory on `Compilation`. local_cache, + build_root, /// `sub_path` is not relative to any of the roots listed above. /// It is resolved starting with `Directories.cwd`; so it is an absolute path on most /// targets, but cwd-relative on WASI. We do not make it cwd-relative on other targets @@ -439,6 +440,7 @@ pub const Path = struct { .zig_lib => dirs.zig_lib.handle, .global_cache => dirs.global_cache.handle, .local_cache => dirs.local_cache.handle, + .build_root => dirs.build_root.handle, }; if (p.sub_path.len == 0) return .{ dir, "." }; assert(!fs.path.isAbsolute(p.sub_path)); @@ -457,6 +459,7 @@ pub const Path = struct { .zig_lib => f.comp.dirs.zig_lib.path orelse ".", .global_cache => f.comp.dirs.global_cache.path orelse ".", .local_cache => f.comp.dirs.local_cache.path orelse ".", + .build_root => f.comp.dirs.build_root.path orelse ".", .none => { const cwd_sub_path = absToCwdRelative(f.p.sub_path, f.comp.dirs.cwd); try w.writeAll(cwd_sub_path); @@ -581,6 +584,7 @@ pub const Path = struct { .zig_lib => dirs.zig_lib.path orelse "", .global_cache => dirs.global_cache.path orelse "", .local_cache => dirs.local_cache.path orelse "", + .build_root => dirs.build_root.path orelse "", .none => "", }, sub_path, @@ -603,6 +607,7 @@ pub const Path = struct { .zig_lib => dirs.zig_lib.path orelse "", .global_cache => dirs.global_cache.path orelse "", .local_cache => dirs.local_cache.path orelse "", + .build_root => dirs.build_root.path orelse "", .none => "", }, p.sub_path, @@ -622,6 +627,7 @@ pub const Path = struct { .zig_lib => dirs.zig_lib.path orelse "", .global_cache => dirs.global_cache.path orelse "", .local_cache => dirs.local_cache.path orelse "", + .build_root => dirs.build_root.path orelse "", .none => "", }, p.sub_path, @@ -635,6 +641,7 @@ pub const Path = struct { .zig_lib => dirs.zig_lib, .global_cache => dirs.global_cache, .local_cache => dirs.local_cache, + .build_root => dirs.build_root, else => { const cwd_sub_path = absToCwdRelative(p.sub_path, dirs.cwd); return .{ @@ -658,13 +665,10 @@ pub const Path = struct { .zig_lib => dirs.zig_lib.path orelse "", .global_cache => dirs.global_cache.path orelse "", .local_cache => dirs.local_cache.path orelse "", + .build_root => dirs.build_root.path orelse "", .none => "", }; - return fs.path.resolve(gpa, &.{ - dirs.cwd, - root_path, - p.sub_path, - }); + return fs.path.resolve(gpa, &.{ dirs.cwd, root_path, p.sub_path }); } pub fn isNested(inner: Path, outer: Path) union(enum) { @@ -1348,7 +1352,7 @@ pub const CacheMode = enum { pub const ParentWholeCache = struct { manifest: *Cache.Manifest, mutex: *std.Io.Mutex, - prefix_map: [4]u8, + prefix_map: [5]u8, }; const CacheUse = union(CacheMode) { @@ -1926,6 +1930,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, } const error_limit = options.error_limit orelse (std.math.maxInt(u16) - 1); + const main_mod = options.main_mod orelse options.root_mod; // We put everything into the cache hash that *cannot be modified // during an incremental update*. For example, one cannot change the @@ -1944,11 +1949,18 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, }, .cwd = options.dirs.cwd, }; - // These correspond to std.zig.Server.Message.PathPrefix. + comptime assert(0 == @backingInt(std.zig.Server.Message.PathPrefix.cwd)); + comptime assert(1 == @backingInt(std.zig.Server.Message.PathPrefix.zig_lib)); + comptime assert(2 == @backingInt(std.zig.Server.Message.PathPrefix.local_cache)); + comptime assert(3 == @backingInt(std.zig.Server.Message.PathPrefix.global_cache)); + comptime assert(4 == @backingInt(std.zig.Server.Message.PathPrefix.build_root)); + comptime assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == 5); cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); cache.addPrefix(options.dirs.zig_lib); cache.addPrefix(options.dirs.local_cache); cache.addPrefix(options.dirs.global_cache); + log.debug("addPrefix build_root {s}", .{options.dirs.build_root.path orelse "(null)"}); + cache.addPrefix(options.dirs.build_root); errdefer cache.manifest_dir.close(io); // This is shared hasher state common to zig source and all C source files. @@ -1984,7 +1996,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, cache.hash.add(options.emit_docs != .no); // TODO audit this and make sure everything is in it - const main_mod = options.main_mod orelse options.root_mod; const comp = try arena.create(Compilation); const opt_zcu: ?*Zcu = if (have_zcu) blk: { // Pre-open the directory handles for cached ZIR code so that it does not need @@ -3116,6 +3127,7 @@ pub fn appendFileSystemInput(comp: *Compilation, path: Compilation.Path) Allocat .zig_lib => comp.dirs.zig_lib, .global_cache => comp.dirs.global_cache, .local_cache => comp.dirs.local_cache, + .build_root => comp.dirs.build_root, .none => .cwd(), }; const prefix: u8 = for (prefixes, 1..) |prefix_dir, i| { @@ -3123,8 +3135,8 @@ pub fn appendFileSystemInput(comp: *Compilation, path: Compilation.Path) Allocat break @intCast(i); } } else std.debug.panic( - "missing prefix directory '{s}' ('{f}') for '{s}'", - .{ @tagName(path.root), want_prefix_dir, path.sub_path }, + "missing prefix directory {t} ('{f}') for {q}", + .{ path.root, want_prefix_dir, path.sub_path }, ); // There may be concurrent calls to this function from C object workers and/or the main thread. @@ -7323,6 +7335,7 @@ fn buildOutputFromZig( 1, // zig lib dir is the same 3, // local cache is mapped to global cache 3, // global cache is the same + 0, // build root is not provided }, }, .incremental, .none => null, diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 56de07bed5a44a25590f77de9ed10b2ae420d8b0..8b355ee22a5895eb65ef581aa536689078379315 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -481,7 +481,7 @@ pub fn updateFile( const stat = try source_file.stat(io); const want_local_cache = switch (file.path.root) { - .none, .local_cache => true, + .none, .local_cache, .build_root => true, .global_cache, .zig_lib => false, }; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 8bb33f83e0d0911b2f427ede147964df5c73cf9a..9e6c29c94c5cfdab42e3822d5a62e5e279d510c8 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1701,6 +1701,7 @@ pub const Object = struct { .zig_lib => dirs.zig_lib.path, .global_cache => dirs.global_cache.path, .local_cache => dirs.local_cache.path, + .build_root => dirs.build_root.path, .none => null, }; diff --git a/src/main.zig b/src/main.zig index d2068b8cdbb6a53f63dd28ac667503f7495e777e..eb5ae7d15354880032f098a121e8099578ea32c3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -423,17 +423,16 @@ fn mainArgs( .wasi => {}, else => process.executablePathAlloc(io, arena) catch |err| fatal("unable to find zig self exe path: {t}", .{err}), }; - var dirs: std.zig.Directories = .init( - arena, - io, - EnvVar.ZIG_LIB_DIR.get(environ_map), - EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map), - .global, - preopens, - self_exe_path, - environ_map, - try std.zig.getResolvedCwd(io, arena), - ); + var dirs: std.zig.Directories = .init(arena, io, .{ + .override_zig_lib = EnvVar.ZIG_LIB_DIR.get(environ_map), + .override_global_cache = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map), + .build_root = null, + .local_cache_strat = .global, + .preopens = preopens, + .self_exe_path = self_exe_path, + .environ_map = environ_map, + .cwd = try std.zig.getResolvedCwd(io, arena), + }); defer dirs.deinit(io); const host = std.zig.resolveTargetQueryOrFatal(io, .{}); var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); @@ -458,17 +457,16 @@ fn mainArgs( .wasi => args[0], else => process.executablePathAlloc(io, arena) catch |err| fatal("unable to find zig self exe path: {t}", .{err}), }; - var dirs: std.zig.Directories = .init( - arena, - io, - EnvVar.ZIG_LIB_DIR.get(environ_map), - EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map), - .global, - preopens, - if (native_os != .wasi) self_exe_path, - environ_map, - try std.zig.getResolvedCwd(io, arena), - ); + var dirs: std.zig.Directories = .init(arena, io, .{ + .override_zig_lib = EnvVar.ZIG_LIB_DIR.get(environ_map), + .override_global_cache = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map), + .build_root = null, + .local_cache_strat = .global, + .preopens = preopens, + .self_exe_path = if (native_os != .wasi) self_exe_path, + .environ_map = environ_map, + .cwd = try std.zig.getResolvedCwd(io, arena), + }); defer dirs.deinit(io); const host = std.zig.resolveTargetQueryOrFatal(io, .{}); var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); @@ -511,7 +509,7 @@ fn mainArgs( } } -const usage_build_generic = +const compile_usage = \\Usage: zig build-exe [options] [files] \\ zig build-lib [options] [files] \\ zig build-obj [options] [files] @@ -565,6 +563,7 @@ const usage_build_generic = \\ --cache-dir [path] Override the local cache directory \\ --global-cache-dir [path] Override the global cache directory \\ --zig-lib-dir [path] Override path to Zig installation lib directory + \\ --build-root [path] Override path to project source files \\ \\Global Compile Options: \\ --name [name] Compilation unit name (not a file path) @@ -1054,6 +1053,7 @@ fn buildOutputType( var rc_includes: std.zig.RcIncludes = .any; var manifest_file: ?[]const u8 = null; var linker_export_symbol_names: std.ArrayList([]const u8) = .empty; + var build_root_path: ?[]const u8 = null; // Tracks the position in c_source_files which have already their owner populated. var c_source_files_owner_index: usize = 0; @@ -1167,7 +1167,7 @@ fn buildOutputType( fatal("unable to read response file {q}: {t}", .{ resp_file_path, err }); } else if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - try Io.File.stdout().writeStreamingAll(io, usage_build_generic); + try Io.File.stdout().writeStreamingAll(io, compile_usage); return cleanExit(io); } else if (mem.eql(u8, arg, "--")) { if (arg_mode == .run) { @@ -1440,6 +1440,8 @@ fn buildOutputType( override_global_cache_dir = args_iter.nextOrFatal(); } else if (mem.eql(u8, arg, "--zig-lib-dir")) { override_lib_dir = args_iter.nextOrFatal(); + } else if (mem.eql(u8, arg, "--build-root")) { + build_root_path = args_iter.nextOrFatal(); } else if (mem.eql(u8, arg, "--debug-log")) { try addDebugLog(arena, args_iter.nextOrFatal()); } else if (mem.eql(u8, arg, "--listen")) { @@ -3254,23 +3256,22 @@ fn buildOutputType( const cwd_path = try std.zig.getResolvedCwd(io, arena); // This `init` calls `fatal` on error. - var dirs: std.zig.Directories = .init( - arena, - io, - override_lib_dir, - override_global_cache_dir, - s: { + var dirs: std.zig.Directories = .init(arena, io, .{ + .override_zig_lib = override_lib_dir, + .override_global_cache = override_global_cache_dir, + .build_root = build_root_path, + .local_cache_strat = s: { if (override_local_cache_dir) |p| break :s .{ .override = p }; break :s switch (arg_mode) { .run => .global, else => .search, }; }, - preopens, - self_exe_path, - environ_map, - cwd_path, - ); + .preopens = preopens, + .self_exe_path = self_exe_path, + .environ_map = environ_map, + .cwd = cwd_path, + }); defer dirs.deinit(io); if (linker_optimization) |o| warn("ignoring deprecated linker optimization setting {q}", .{o}); @@ -5020,17 +5021,16 @@ fn jitCmdInner( const cwd_path = try std.zig.getResolvedCwd(io, arena); // This `init` calls `fatal` on error. - var dirs: std.zig.Directories = .init( - arena, - io, - override_lib_dir, - override_global_cache_dir, - .global, - preopens, - self_exe_path, - environ_map, - cwd_path, - ); + var dirs: std.zig.Directories = .init(arena, io, .{ + .override_zig_lib = override_lib_dir, + .override_global_cache = override_global_cache_dir, + .build_root = null, + .local_cache_strat = .global, + .preopens = preopens, + .self_exe_path = self_exe_path, + .environ_map = environ_map, + .cwd = cwd_path, + }); defer dirs.deinit(io); var child_argv: std.ArrayList([]const u8) = .empty; -- 2.54.0 From e4b624eb7663ff3f3e10bff48024e7955d92b204 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 17 Aug 2026 18:02:03 -0700 Subject: [PATCH 06/14] Maker: pass --build-root to configurer also make --verbose work for compiling configurer --- lib/compiler/Maker.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig index 0a0b68aabbe65d31f6f24518c63aa40e9bf387ee..0a8f9824e62d35dbe08bee9305ac4a8f385d2ea7 100644 --- a/lib/compiler/Maker.zig +++ b/lib/compiler/Maker.zig @@ -1120,6 +1120,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { graph.zig_exe, "build-exe", // "--cache-dir", graph.local_cache_root.path orelse ".", // "--global-cache-dir", graph.global_cache_root.path orelse ".", // + "--build-root", graph.build_root_directory.path orelse ".", // "--zig-lib-dir", graph.zig_lib_directory.path orelse ".", // "--name", configurer_exe_name, // "-fsingle-threaded", // @@ -1424,6 +1425,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { break :cp .{ path, man.toOwnedLock() }; } } + try graph.handleVerbose(null, null, build_configurer_argv.items); const configure_exe_path: Path = if (std.zig.buildExeSubprocess(gpa, io, .{ .argv = build_configurer_argv.items, .cache_root = graph.local_cache_root, -- 2.54.0 From e8090258b5ca045d7b89fdf625115477514880e0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 17 Aug 2026 18:03:23 -0700 Subject: [PATCH 07/14] 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. --- lib/std/Build/Cache.zig | 37 +++++++++++-------- src/Compilation.zig | 78 ++++++++++++++++++++++++++++++++++++----- src/Zcu/PerThread.zig | 14 +++----- src/codegen/llvm.zig | 2 +- src/link/Dwarf.zig | 2 +- 5 files changed, 98 insertions(+), 35 deletions(-) diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index 2391e698c4158197c63db208cceb0618b342a208..9019cf5ec37b4702b49c60562f85785fdbad19a7 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -1032,24 +1032,32 @@ pub const Manifest = struct { /// Like `addFilePost` but when the file contents have already been loaded from disk. pub fn addFilePostContents( - self: *Manifest, + man: *Manifest, file_path: []const u8, bytes: []const u8, stat: File.Stat, ) !void { - assert(self.manifest_file != null); - const gpa = self.cache.gpa; + assert(man.manifest_file != null); + const gpa = man.cache.gpa; + const prefixed_path = try man.cache.findPrefix(file_path); + var keep = false; + defer if (!keep) gpa.free(prefixed_path.sub_path); + keep = try addPrefixedPathPostContents(man, prefixed_path, bytes, stat); + } - const prefixed_path = try self.cache.findPrefix(file_path); - errdefer gpa.free(prefixed_path.sub_path); + /// Low level function. `prefixed_path` references cloned memory. Returns + /// whether or not `prefixed_path.sub_path` should be kept. + pub fn addPrefixedPathPostContents( + man: *Manifest, + prefixed_path: PrefixedPath, + bytes: []const u8, + stat: File.Stat, + ) !bool { + const gpa = man.cache.gpa; + const gop = try man.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{}); + errdefer _ = man.files.pop(); - const gop = try self.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{}); - errdefer _ = self.files.pop(); - - if (gop.found_existing) { - gpa.free(prefixed_path.sub_path); - return; - } + if (gop.found_existing) return false; const new_file = gop.key_ptr; @@ -1062,7 +1070,7 @@ pub const Manifest = struct { .contents = null, }; - if (try self.isProblematicTimestamp(new_file.stat.mtime)) { + if (try man.isProblematicTimestamp(new_file.stat.mtime)) { // The actual file has an unreliable timestamp, force it to be hashed new_file.stat.mtime = .zero; new_file.stat.inode = 0; @@ -1074,7 +1082,8 @@ pub const Manifest = struct { hasher.final(&new_file.bin_digest); } - self.hash.hasher.update(&new_file.bin_digest); + man.hash.hasher.update(&new_file.bin_digest); + return true; } pub fn addDepFilePost(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void { diff --git a/src/Compilation.zig b/src/Compilation.zig index 92d77b97f882816fae92bf88c8db3d18b1e37ec5..b0df1aaeade37e0e53231aa3e35f490f4ea642d9 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -506,10 +506,11 @@ pub const Path = struct { // so that we prefer `.root = .local_cache` over `.root = .zig_lib`. The easiest way to do // this is simply to prioritize the longest root path. const PathAndRoot = struct { ?[]const u8, Root }; - var roots: [3]PathAndRoot = .{ + var roots: [4]PathAndRoot = .{ .{ dirs.zig_lib.path, .zig_lib }, .{ dirs.global_cache.path, .global_cache }, .{ dirs.local_cache.path, .local_cache }, + .{ dirs.build_root.path, .build_root }, }; // This must be a stable sort, because the global and local cache directories may be the same, in // which case we need to make a consistent choice. @@ -660,7 +661,7 @@ pub const Path = struct { /// This should not be used for most of the compiler pipeline, but is useful when emitting /// paths from the compilation (e.g. in debug info), because they will not depend on the cwd. /// The returned path is owned by the caller and allocated into `gpa`. - pub fn toAbsolute(p: Path, dirs: std.zig.Directories, gpa: Allocator) Allocator.Error![]u8 { + pub fn toAbsolute(p: Path, dirs: *const std.zig.Directories, gpa: Allocator) Allocator.Error![]u8 { const root_path: []const u8 = switch (p.root) { .zig_lib => dirs.zig_lib.path orelse "", .global_cache => dirs.global_cache.path orelse "", @@ -701,6 +702,66 @@ pub const Path = struct { .no, .different_roots => false, }; } + + pub fn addToCacheManifestPostHit(p: Path, man: *Cache.Manifest, dirs: *const std.zig.Directories) !void { + comptime assert(0 == @backingInt(std.zig.Server.Message.PathPrefix.cwd)); + comptime assert(1 == @backingInt(std.zig.Server.Message.PathPrefix.zig_lib)); + comptime assert(2 == @backingInt(std.zig.Server.Message.PathPrefix.local_cache)); + comptime assert(3 == @backingInt(std.zig.Server.Message.PathPrefix.global_cache)); + comptime assert(4 == @backingInt(std.zig.Server.Message.PathPrefix.build_root)); + comptime assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == 5); + const gpa = man.cache.gpa; + const prefixed_path: Cache.PrefixedPath = .{ + .prefix = switch (p.root) { + .none => { + const path = try p.toAbsolute(dirs, gpa); + defer gpa.free(path); + return man.addFilePost(path); + }, + .zig_lib => 1, + .local_cache => 2, + .global_cache => 3, + .build_root => 4, + }, + .sub_path = try gpa.dupe(u8, p.sub_path), + }; + var keep = false; + defer if (!keep) gpa.free(prefixed_path.sub_path); + keep = try man.addPrefixedPathPost(prefixed_path); + } + + pub fn addToCacheManifestPostHitContents( + p: Path, + man: *Cache.Manifest, + dirs: *const std.zig.Directories, + bytes: []const u8, + stat: Cache.File.Stat, + ) !void { + comptime assert(0 == @backingInt(std.zig.Server.Message.PathPrefix.cwd)); + comptime assert(1 == @backingInt(std.zig.Server.Message.PathPrefix.zig_lib)); + comptime assert(2 == @backingInt(std.zig.Server.Message.PathPrefix.local_cache)); + comptime assert(3 == @backingInt(std.zig.Server.Message.PathPrefix.global_cache)); + comptime assert(4 == @backingInt(std.zig.Server.Message.PathPrefix.build_root)); + comptime assert(@typeInfo(std.zig.Server.Message.PathPrefix).@"enum".field_names.len == 5); + const gpa = man.cache.gpa; + const prefixed_path: Cache.PrefixedPath = .{ + .prefix = switch (p.root) { + .none => { + const path = try p.toAbsolute(dirs, gpa); + defer gpa.free(path); + return man.addFilePostContents(path, bytes, stat); + }, + .zig_lib => 1, + .local_cache => 2, + .global_cache => 3, + .build_root => 4, + }, + .sub_path = try gpa.dupe(u8, p.sub_path), + }; + var keep = false; + defer if (!keep) gpa.free(prefixed_path.sub_path); + keep = try man.addPrefixedPathPostContents(prefixed_path, bytes, stat); + } }; /// 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 man = comp.cache_parent.obtain(); whole.cache_manifest = &man; - try addNonIncrementalStuffToCacheManifest(comp, arena, &man); + try addNonIncrementalStuffToCacheManifest(comp, &man); // Under `--time-report`, ignore cache hits; do the work anyway for those juicy numbers. const ignore_hit = comp.time_report != null; @@ -3328,15 +3389,14 @@ fn renameTmpIntoCache( /// anything from the link cache manifest. pub const link_hash_implementation_version = 14; -fn addNonIncrementalStuffToCacheManifest( - comp: *Compilation, - arena: Allocator, - man: *Cache.Manifest, -) !void { +fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifest) !void { comptime assert(link_hash_implementation_version == 14); if (comp.zcu) |zcu| { - try addModuleTableToCacheHash(zcu, arena, &man.hash, .{ .files = man }); + // No need to call `addModuleTableToCacheHash` here 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. // Synchronize with other matching comments: ZigOnlyHashStuff man.hash.addListOfBytes(comp.test_filters); diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 8b355ee22a5895eb65ef581aa536689078379315..0e4962985a566aa611a326ea1aeb2b61e9928ae4 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -221,22 +221,19 @@ pub fn update( .astgen_failure, .success => {}, // the file was read successfully } - const path = try file.path.toAbsolute(comp.dirs, gpa); - defer gpa.free(path); - const result = res: { try whole.cache_manifest_mutex.lock(io); defer whole.cache_manifest_mutex.unlock(io); if (file.source) |source| { - break :res man.addFilePostContents(path, source, file.stat); + break :res file.path.addToCacheManifestPostHitContents(man, &comp.dirs, source, file.stat); } else { - break :res man.addFilePost(path); + break :res file.path.addToCacheManifestPostHit(man, &comp.dirs); } }; result catch |err| switch (err) { error.OutOfMemory => |e| return e, else => { - try pt.reportRetryableFileError(file_index, "unable to update cache: {s}", .{@errorName(err)}); + try pt.reportRetryableFileError(file_index, "unable to update cache: {t}", .{err}); continue; }, }; @@ -2965,13 +2962,10 @@ fn newEmbedFile( const array_len = Value.fromInterned(new_file.val).typeOf(zcu).childType(zcu).arrayLen(zcu); const contents = ip_str.toSlice(array_len, ip); - const path_str = try path.toAbsolute(comp.dirs, gpa); - defer gpa.free(path_str); - try whole.cache_manifest_mutex.lock(io); defer whole.cache_manifest_mutex.unlock(io); - try man.addFilePostContents(path_str, contents, new_file.stat); + try path.addToCacheManifestPostHitContents(man, &comp.dirs, contents, new_file.stat); } return new_file; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 9e6c29c94c5cfdab42e3822d5a62e5e279d510c8..4022a2b901c77443b977a4461e044de40a8230c9 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -481,7 +481,7 @@ pub const Object = struct { // way already, but here we throw all that sweet information // into the garbage can by converting into absolute paths. What // a terrible tragedy. - const compile_unit_dir = try zcu.main_mod.root.toAbsolute(comp.dirs, arena); + const compile_unit_dir = try zcu.main_mod.root.toAbsolute(&comp.dirs, arena); const debug_file = try builder.debugFile( try builder.metadataString(comp.root_name), diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index c8eef4c8651367edbc1abffcd3cf24d76b847603..a73874ea0259b566444233452028d36ec99479e3 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -4735,7 +4735,7 @@ fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (UpdateError || Writer.Err } for (dwarf.mods.keys(), dwarf.mods.values()) |mod, *mod_info| { - const root_dir_path = try mod.root.toAbsolute(zcu.comp.dirs, dwarf.gpa); + const root_dir_path = try mod.root.toAbsolute(&zcu.comp.dirs, dwarf.gpa); defer dwarf.gpa.free(root_dir_path); mod_info.root_dir_path = try dwarf.debug_line_str.addString(dwarf, root_dir_path); } -- 2.54.0 From e820cf38ed05702aa08c3b61bd0bc5cdc4bc74b0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 17 Aug 2026 18:26:10 -0700 Subject: [PATCH 08/14] std.zig.Directories: fix deinit of build_root --- lib/std/zig.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 4853d43f93a2814160ba278fa04fa6ef2d011102..c05595852f46a7b3f4d894b41655e2ea08c924e3 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -1291,11 +1291,12 @@ pub const Directories = struct { pub fn deinit(dirs: *Directories, io: Io) void { // The local and global caches could be the same. const close_local = dirs.local_cache.handle.handle != dirs.global_cache.handle.handle; + const close_build_root = dirs.build_root.handle.handle != Io.Dir.cwd().handle; dirs.global_cache.handle.close(io); if (close_local) dirs.local_cache.handle.close(io); dirs.zig_lib.handle.close(io); - if (dirs.build_root.path != null) dirs.build_root.handle.close(io); + if (close_build_root) dirs.build_root.handle.close(io); } /// Returns a `Directories` where `local_cache` is replaced with `global_cache`, intended for @@ -1346,7 +1347,6 @@ pub const Directories = struct { }; }; const build_root: Cache.Directory = if (options.build_root) |s| - // TODO this ends up setting path to null, leaking the fd in deinit openUnresolved(arena, io, cwd, s, .@"build root") else .cwd(); -- 2.54.0 From 5a6c3aaedfef1ff242317f622252e0c7eebf0159 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 17 Aug 2026 18:34:10 -0700 Subject: [PATCH 09/14] remove some debug logs --- lib/compiler/Maker.zig | 1 - lib/std/zig.zig | 1 - src/Compilation.zig | 1 - 3 files changed, 3 deletions(-) diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig index 0a8f9824e62d35dbe08bee9305ac4a8f385d2ea7..b5132c6ad05787c9318d649beb2549b7282de07f 100644 --- a/lib/compiler/Maker.zig +++ b/lib/compiler/Maker.zig @@ -1415,7 +1415,6 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { if (config_man) |man| { if (try man.hit(compile_prog_node)) { - log.debug("configuration cache hit", .{}); const digest = man.final(); const path: Path = .{ .root_dir = graph.local_cache_root, diff --git a/lib/std/zig.zig b/lib/std/zig.zig index c05595852f46a7b3f4d894b41655e2ea08c924e3..81baffb36bc70dd6551b4acfd360d2a811db2629 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -1763,7 +1763,6 @@ pub fn buildExeSubprocess( const sub_path = try gpa.dupe(u8, prefixed_path[1..]); var keep = false; defer if (!keep) gpa.free(sub_path); - log.debug("file system input: {t} {s}", .{ prefix, sub_path }); keep = man.addPrefixedPathPost(.{ .prefix = @backingInt(prefix), .sub_path = sub_path, diff --git a/src/Compilation.zig b/src/Compilation.zig index b0df1aaeade37e0e53231aa3e35f490f4ea642d9..e9316d7eb68c2dbf8305ab1691bb811ce8ca8d50 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2020,7 +2020,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, cache.addPrefix(options.dirs.zig_lib); cache.addPrefix(options.dirs.local_cache); cache.addPrefix(options.dirs.global_cache); - log.debug("addPrefix build_root {s}", .{options.dirs.build_root.path orelse "(null)"}); cache.addPrefix(options.dirs.build_root); errdefer cache.manifest_dir.close(io); -- 2.54.0 From 5b8bee462a63538083e7b2e16f1dae7b39c8529b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 17 Aug 2026 19:11:06 -0700 Subject: [PATCH 10/14] mingw: fix path usage - better reporting of cache checking failure - fix not using Path properly - fix not using Path properly in Preprocessor code --- src/Compilation.zig | 3 +- src/libs/mingw.zig | 61 ++++++++++++++++++++------------- src/libs/mingw/Preprocessor.zig | 34 +++++++----------- 3 files changed, 51 insertions(+), 47 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index e9316d7eb68c2dbf8305ab1691bb811ce8ca8d50..58273389905e37175d60191b613ffebd7dd673a3 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -5351,6 +5351,7 @@ fn buildMingwCrtFile(comp: *Compilation, crt_file: mingw.CrtFile, prog_node: std fn buildMingwImportLib(comp: *Compilation, lib_name: []const u8, is_prelink: bool, prog_node: std.Progress.Node) void { const crt_file_path = mingw.buildImportLib(comp, lib_name, prog_node) catch |err| switch (err) { + error.AlreadyReported => return, // TODO: This isn't actually true for self-hosted // In the non-prelink case we will end up putting foo.lib onto the linker line and letting the linker // use its library paths to look for libraries and report any problems. @@ -5364,7 +5365,7 @@ fn buildMingwImportLib(comp: *Compilation, lib_name: []const u8, is_prelink: boo // TODO Surface more error details. else => |e| return comp.lockAndSetMiscFailure( .windows_import_lib, - "unable to generate mingw DLL import .lib file for {s}: {t}", + "generating mingw DLL import .lib file for {s} failed: {t}", .{ lib_name, e }, ), }; diff --git a/src/libs/mingw.zig b/src/libs/mingw.zig index 73e741c4fa4e97fa2622ed9e13c43d59568bd89c..a91c24291d0a395b189712c4d8f199bf13cd57d0 100644 --- a/src/libs/mingw.zig +++ b/src/libs/mingw.zig @@ -215,12 +215,15 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8, prog_node: std.P defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - const def_file_path = findDef(arena, io, comp.getTarget(), comp.dirs.zig_lib, lib_name) catch |err| switch (err) { - error.FileNotFound => return error.DefNotFound, - else => |e| return e, + const def_file_path: Cache.Path = .{ + .root_dir = comp.dirs.zig_lib, + .sub_path = findDef(arena, io, comp.getTarget(), comp.dirs.zig_lib, lib_name) catch |err| switch (err) { + error.FileNotFound => return error.DefNotFound, + else => |e| return e, + }, }; // Only .def.in files need preprocessing - const def_needs_preprocessing = mem.endsWith(u8, def_file_path, ".def.in"); + const def_needs_preprocessing = mem.endsWith(u8, def_file_path.sub_path, ".def.in"); const target = comp.getTarget(); @@ -243,15 +246,34 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8, prog_node: std.P var man = cache.obtain(); defer man.deinit(); - _ = try man.addFilePath(.{ - .root_dir = comp.dirs.zig_lib, - .sub_path = def_file_path, - }, null); + _ = try man.addFilePath(def_file_path, null); const final_lib_basename = try std.fmt.allocPrint(gpa, "{s}.lib", .{lib_name}); errdefer gpa.free(final_lib_basename); - if (try man.hit(prog_node)) { + const is_hit = man.hit(prog_node) catch |err| switch (err) { + error.CacheCheckFailed => switch (man.diagnostic) { + .none => unreachable, + .manifest_create, .manifest_read, .manifest_lock => |e| { + comp.setMiscFailure(.windows_import_lib, "checking cache failed: {t} {t}", .{ man.diagnostic, e }); + return error.AlreadyReported; + }, + .file_open, .file_stat, .file_read, .file_hash => |op| { + const pp = man.files.keys()[op.file_index].prefixed_path; + const prefix = man.cache.prefixes()[pp.prefix]; + comp.setMiscFailure(.windows_import_lib, "checking cache failed: {f}{s} {t} {t}", .{ + prefix, pp.sub_path, man.diagnostic, op.err, + }); + return error.AlreadyReported; + }, + }, + error.OutOfMemory, error.Canceled => |e| return e, + error.InvalidFormat => { + comp.setMiscFailure(.windows_import_lib, "checking cache failed: invalid manifest file format", .{}); + return error.AlreadyReported; + }, + }; + if (is_hit) { const digest = man.final(); const sub_path = try std.fs.path.join(gpa, &.{ "o", &digest, final_lib_basename }); errdefer gpa.free(sub_path); @@ -276,20 +298,11 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8, prog_node: std.P var o_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, o_sub_path, .{}); defer o_dir.close(io); - const include_dir = try comp.dirs.zig_lib.join(arena, &.{ "libc", "mingw", "def-include" }); - - if (comp.verbose_cc) { - var buffer: [256]u8 = undefined; - const stderr = try io.lockStderr(&buffer, null); - defer io.unlockStderr(); - const w = &stderr.file_writer.interface; - w.print("def file: {s}\n", .{def_file_path}) catch |err| switch (err) { - error.WriteFailed => return stderr.file_writer.err.?, - }; - w.print("include dir: {s}\n", .{include_dir}) catch |err| switch (err) { - error.WriteFailed => return stderr.file_writer.err.?, - }; - } + const sep = path.sep_str; + const include_dir: Cache.Path = .{ + .root_dir = comp.dirs.zig_lib, + .sub_path = "libc" ++ sep ++ "mingw" ++ sep ++ "def-include", + }; const members = members: { const members_node = sub_node.start("Members", 0); @@ -313,7 +326,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8, prog_node: std.P break :pp try aw.toOwnedSliceSentinel(0); }, - false => try Io.Dir.cwd().readFileAllocOptions(io, def_file_path, gpa, .unlimited, .of(u8), 0), + false => try def_file_path.root_dir.handle.readFileAllocOptions(io, def_file_path.sub_path, gpa, .unlimited, .of(u8), 0), }; defer gpa.free(input); diff --git a/src/libs/mingw/Preprocessor.zig b/src/libs/mingw/Preprocessor.zig index f6606eb54cd0f2ea46a85194dd79674115256499..f10cb33d39853efb6455e74937a75a7c34296cca 100644 --- a/src/libs/mingw/Preprocessor.zig +++ b/src/libs/mingw/Preprocessor.zig @@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator; const Token = Tokenizer.Token; const mem = std.mem; const assert = std.debug.assert; +const Path = std.Build.Cache.Path; test { _ = Tokenizer; @@ -25,15 +26,15 @@ pub const Source = struct { pub const generated: Source.Id = std.math.maxInt(usize); pub const Id = usize; id: Id = generated, - path: []const u8, + path: Path, buf: []const u8, }; -sources: std.array_hash_map.String(Source) = .empty, +sources: std.array_hash_map.Custom(Path, Source, Path.TableAdapter, false) = .empty, arena: Allocator, io: std.Io, -include_dir: []const u8, +include_dir: Path, top_expansion_buf: ExpandBuf = .empty, add_expansion_nl: usize = 0, @@ -132,7 +133,7 @@ fn defineBuiltin(pp: *Preprocessor, name: []const u8) !void { }); } -pub fn preprocess(pp: *Preprocessor, file_path: []const u8) !void { +pub fn preprocess(pp: *Preprocessor, file_path: Path) !void { const source = try pp.addSourceFromPath(file_path); try pp.preprocessFile(source); } @@ -789,13 +790,9 @@ fn makeGeneratedToken( return pasted_token; } -fn findInclude( - pp: *Preprocessor, - filename: []const u8, - includer_token: Token, -) !?Source { +fn findInclude(pp: *Preprocessor, filename: []const u8, includer_token: Token) !?Source { const other_file = pp.sources.values()[includer_token.source].path; - const dir = std.fs.path.dirname(other_file) orelse "."; + const dir: Path = other_file.dirname() orelse .cwd(); if (try pp.checkIncludeDir(filename, dir)) |res| return res; return pp.checkIncludeDir(filename, pp.include_dir); @@ -804,31 +801,24 @@ fn findInclude( fn checkIncludeDir( pp: *Preprocessor, include_path: []const u8, - include_dir: []const u8, + include_dir: Path, ) !?Source { - const format = "{s}{c}{s}"; var bfa_buf: [1024]u8 = undefined; var bfa_state: std.heap.BufferFirstAllocator = .init(&bfa_buf, pp.arena); const bfa = bfa_state.allocator(); - const header_path = try std.fmt.allocPrint(bfa, format, .{ - include_dir, - std.fs.path.sep, - include_path, - }); - defer bfa.free(header_path); - + const header_path = try include_dir.join(bfa, include_path); return pp.addSourceFromPath(header_path) catch |err| switch (err) { error.OutOfMemory => |e| return e, else => return null, }; } -pub fn addSourceFromPath(pp: *Preprocessor, path: []const u8) !Source { +pub fn addSourceFromPath(pp: *Preprocessor, path: Path) !Source { if (pp.sources.get(path)) |src| return src; try pp.sources.ensureUnusedCapacity(pp.arena, 1); - const contents = try std.Io.Dir.cwd().readFileAlloc(pp.io, path, pp.arena, .limited(std.math.maxInt(u32))); - const duped_path = try pp.arena.dupe(u8, path); + const contents = try path.root_dir.handle.readFileAlloc(pp.io, path.sub_path, pp.arena, .limited(std.math.maxInt(u32))); + const duped_path = try path.clone(pp.arena); const src: Source = .{ .buf = contents, -- 2.54.0 From 297774ed7c2f337e1528a99abf0e089718363768 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 17 Aug 2026 19:18:24 -0700 Subject: [PATCH 11/14] Compilation: still hash the module table names and relative paths otherwise if you did nothing except swap the import names of two modules, you would get a false positive cache hit --- src/Compilation.zig | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 58273389905e37175d60191b613ffebd7dd673a3..9b10e2a2cbe4b433d96f7979bf0d492c27a3c563 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1678,15 +1678,7 @@ pub const CreateOptions = struct { }; }; -fn addModuleTableToCacheHash( - zcu: *Zcu, - arena: Allocator, - hash: *Cache.HashHelper, - hash_type: union(enum) { path_bytes, files: *Cache.Manifest }, -) error{ - OutOfMemory, - Unexpected, -}!void { +fn addModuleTableToCacheHash(zcu: *Zcu, hash: *Cache.HashHelper) error{ OutOfMemory, Unexpected }!void { assert(zcu.module_roots.count() != 0); // module_roots is populated for (zcu.module_roots.keys(), zcu.module_roots.values()) |mod, opt_mod_root_file| { @@ -1695,17 +1687,9 @@ fn addModuleTableToCacheHash( if (zcu.fileByIndex(mod_root_file).is_builtin) continue; // redundant } cache_helpers.addModule(hash, mod); - switch (hash_type) { - .path_bytes => { - hash.add(mod.root.root); - hash.addBytes(mod.root.sub_path); - hash.addBytes(mod.root_src_path); - }, - .files => |man| if (mod.root_src_path.len != 0) { - const root_src_path = try mod.root.toCachePath(zcu.comp.dirs).join(arena, mod.root_src_path); - _ = try man.addFilePath(root_src_path, null); - }, - } + hash.add(mod.root.root); + hash.addBytes(mod.root.sub_path); + hash.addBytes(mod.root_src_path); hash.addListOfBytes(mod.deps.keys()); } } @@ -2336,7 +2320,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, // likely different compilations and therefore this would be likely to // cause cache hits. if (comp.zcu) |zcu| { - try addModuleTableToCacheHash(zcu, arena, &hash, .path_bytes); + try addModuleTableToCacheHash(zcu, &hash); } else { cache_helpers.addModule(&hash, options.root_mod); } @@ -3392,10 +3376,11 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes comptime assert(link_hash_implementation_version == 14); if (comp.zcu) |zcu| { - // No need to call `addModuleTableToCacheHash` here because it is + // No need to hash the actual file contents here 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. + try addModuleTableToCacheHash(zcu, &man.hash); // Synchronize with other matching comments: ZigOnlyHashStuff man.hash.addListOfBytes(comp.test_filters); -- 2.54.0 From 166cf303b260d59174fa090b3dd693ae1a7e351a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 17 Aug 2026 22:53:49 -0700 Subject: [PATCH 12/14] std.Build.Cache: update unit tests to new API --- lib/std/Build/Cache.zig | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index 9019cf5ec37b4702b49c60562f85785fdbad19a7..94051c51658796081d413cc18133a97c93814d55 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -1375,7 +1375,7 @@ test "cache file and then recall it" { ch.hash.add(true); ch.hash.add(@as(u16, 1234)); ch.hash.addBytes("1234"); - _ = try ch.addFile(temp_file, null); + _ = try ch.addFilePath(.initCwd(temp_file), null); // There should be nothing in the cache try testing.expectEqual(false, try ch.hit(.none)); @@ -1390,7 +1390,7 @@ test "cache file and then recall it" { ch.hash.add(true); ch.hash.add(@as(u16, 1234)); ch.hash.addBytes("1234"); - _ = try ch.addFile(temp_file, null); + _ = try ch.addFilePath(.initCwd(temp_file), null); // Cache hit! We just "built" the same file try testing.expect(try ch.hit(.none)); @@ -1443,7 +1443,7 @@ test "check that changing a file makes cache fail" { defer ch.deinit(); ch.hash.addBytes("1234"); - const temp_file_idx = try ch.addFile(temp_file, 100); + const temp_file_idx = try ch.addFilePath(.initCwd(temp_file), 100); // There should be nothing in the cache try testing.expectEqual(false, try ch.hit(.none)); @@ -1462,7 +1462,7 @@ test "check that changing a file makes cache fail" { defer ch.deinit(); ch.hash.addBytes("1234"); - const temp_file_idx = try ch.addFile(temp_file, 100); + const temp_file_idx = try ch.addFilePath(.initCwd(temp_file), 100); // A file that we depend on has been updated, so the cache should not contain an entry for it try testing.expectEqual(false, try ch.hit(.none)); @@ -1570,7 +1570,7 @@ test "Manifest with files added after initial hash work" { defer ch.deinit(); ch.hash.addBytes("1234"); - _ = try ch.addFile(temp_file1, null); + _ = try ch.addFilePath(.initCwd(temp_file1), null); // There should be nothing in the cache try testing.expectEqual(false, try ch.hit(.none)); @@ -1585,7 +1585,7 @@ test "Manifest with files added after initial hash work" { defer ch.deinit(); ch.hash.addBytes("1234"); - _ = try ch.addFile(temp_file1, null); + _ = try ch.addFilePath(.initCwd(temp_file1), null); try testing.expect(try ch.hit(.none)); digest2 = ch.final(); @@ -1608,7 +1608,7 @@ test "Manifest with files added after initial hash work" { defer ch.deinit(); ch.hash.addBytes("1234"); - _ = try ch.addFile(temp_file1, null); + _ = try ch.addFilePath(.initCwd(temp_file1), null); // A file that we depend on has been updated, so the cache should not contain an entry for it try testing.expectEqual(false, try ch.hit(.none)); -- 2.54.0 From 7b0f777ffdd2900e7dcd5db096b27a06bc416263 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 17 Aug 2026 22:53:58 -0700 Subject: [PATCH 13/14] update tools/check_mingw.zig to new API --- tools/check_mingw.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/check_mingw.zig b/tools/check_mingw.zig index c67b3f2599efaa9592cc238b866a44d2b3e1bf45..74b2f1d588ad938a5287d67929c5ba05e7f73ce1 100644 --- a/tools/check_mingw.zig +++ b/tools/check_mingw.zig @@ -71,11 +71,11 @@ pub fn main(init: std.process.Init) !void { var pp: Preprocessor = .{ .io = io, .arena = pp_arena, - .include_dir = mingw_include_path, + .include_dir = .initCwd(mingw_include_path), .target = target, }; - pp.preprocess(file_path) catch |err| { + pp.preprocess(.initCwd(file_path)) catch |err| { std.log.err("error preprocessing file {s} for target {t}: {t}", .{ entry.path, target.cpu.arch, err }); fail = true; continue; -- 2.54.0 From 5ee54cd52ad5f03ce24ec8c0ffee35972df3a5f3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 19 Aug 2026 17:53:08 -0700 Subject: [PATCH 14/14] Compilation: avoid putting superfluous dot in error paths --- src/Compilation.zig | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 9b10e2a2cbe4b433d96f7979bf0d492c27a3c563..fcb65eb66d89daedb4a2cd9e168a1461ad1486af 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -435,7 +435,7 @@ pub const Path = struct { const dir = switch (p.root) { .none => { const cwd_sub_path = absToCwdRelative(p.sub_path, dirs.cwd); - return .{ Io.Dir.cwd(), cwd_sub_path }; + return .{ Io.Dir.cwd(), if (cwd_sub_path.len == 0) "." else cwd_sub_path }; }, .zig_lib => dirs.zig_lib.handle, .global_cache => dirs.global_cache.handle, @@ -456,20 +456,18 @@ pub const Path = struct { comp: *Compilation, pub fn format(f: Formatter, w: *Writer) Writer.Error!void { const root_path: []const u8 = switch (f.p.root) { - .zig_lib => f.comp.dirs.zig_lib.path orelse ".", - .global_cache => f.comp.dirs.global_cache.path orelse ".", - .local_cache => f.comp.dirs.local_cache.path orelse ".", - .build_root => f.comp.dirs.build_root.path orelse ".", + .zig_lib => f.comp.dirs.zig_lib.path orelse "", + .global_cache => f.comp.dirs.global_cache.path orelse "", + .local_cache => f.comp.dirs.local_cache.path orelse "", + .build_root => f.comp.dirs.build_root.path orelse "", .none => { - const cwd_sub_path = absToCwdRelative(f.p.sub_path, f.comp.dirs.cwd); - try w.writeAll(cwd_sub_path); + try w.writeAll(absToCwdRelative(f.p.sub_path, f.comp.dirs.cwd)); return; }, }; - assert(root_path.len != 0); try w.writeAll(root_path); if (f.p.sub_path.len > 0) { - try w.writeByte(fs.path.sep); + if (root_path.len != 0) try w.writeByte(fs.path.sep); try w.writeAll(f.p.sub_path); } } @@ -477,16 +475,16 @@ pub const Path = struct { /// Given the `sub_path` of a `Path` with `Path.root == .none`, attempts to convert /// the (absolute) path to a cwd-relative path. Otherwise, returns the absolute path - /// unmodified. The returned string is never empty: "" is converted to ".". + /// unmodified. The returned string is never "."; empty string will be returned instead. fn absToCwdRelative(sub_path: []const u8, cwd_path: []const u8) []const u8 { if (builtin.target.os.tag == .wasi) { - if (sub_path.len == 0) return "."; + if (sub_path.len == 0) return ""; assert(!fs.path.isAbsolute(sub_path)); return sub_path; } assert(fs.path.isAbsolute(sub_path)); if (!std.mem.startsWith(u8, sub_path, cwd_path)) return sub_path; - if (sub_path.len == cwd_path.len) return "."; // the strings are equal + if (sub_path.len == cwd_path.len) return ""; // the strings are equal const path_sep_index = path_sep_index: { // cwd is just a root, e.g. / or C:\ if (cwd_path[cwd_path.len - 1] == fs.path.sep) break :path_sep_index cwd_path.len - 1; @@ -647,7 +645,7 @@ pub const Path = struct { const cwd_sub_path = absToCwdRelative(p.sub_path, dirs.cwd); return .{ .root_dir = .cwd(), - .sub_path = cwd_sub_path, + .sub_path = if (cwd_sub_path.len == 0) null else cwd_sub_path, }; }, }; -- 2.54.0