Commit 16d78bc0c0

Jacob Young <jacobly0@users.noreply.github.com>
2025-06-13 18:01:59
Build: add install commands to `--verbose` output
1 parent df4068c
lib/std/Build/Step/Compile.zig
@@ -668,6 +668,7 @@ pub fn producesPdbFile(compile: *Compile) bool {
         else => return false,
     }
     if (target.ofmt == .c) return false;
+    if (compile.use_llvm == false) return false;
     if (compile.root_module.strip == true or
         (compile.root_module.strip == null and compile.root_module.optimize == .ReleaseSmall))
     {
lib/std/Build/Step/InstallArtifact.zig
@@ -119,18 +119,12 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
     _ = options;
     const install_artifact: *InstallArtifact = @fieldParentPtr("step", step);
     const b = step.owner;
-    const cwd = fs.cwd();
 
     var all_cached = true;
 
     if (install_artifact.dest_dir) |dest_dir| {
         const full_dest_path = b.getInstallPath(dest_dir, install_artifact.dest_sub_path);
-        const src_path = install_artifact.emitted_bin.?.getPath3(b, step);
-        const p = fs.Dir.updateFile(src_path.root_dir.handle, src_path.sub_path, cwd, full_dest_path, .{}) catch |err| {
-            return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
-                src_path.sub_path, full_dest_path, @errorName(err),
-            });
-        };
+        const p = try step.installFile(install_artifact.emitted_bin.?, full_dest_path);
         all_cached = all_cached and p == .fresh;
 
         if (install_artifact.dylib_symlinks) |dls| {
@@ -141,48 +135,28 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
     }
 
     if (install_artifact.implib_dir) |implib_dir| {
-        const src_path = install_artifact.emitted_implib.?.getPath3(b, step);
-        const full_implib_path = b.getInstallPath(implib_dir, fs.path.basename(src_path.sub_path));
-        const p = fs.Dir.updateFile(src_path.root_dir.handle, src_path.sub_path, cwd, full_implib_path, .{}) catch |err| {
-            return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
-                src_path.sub_path, full_implib_path, @errorName(err),
-            });
-        };
+        const full_implib_path = b.getInstallPath(implib_dir, install_artifact.emitted_implib.?.basename(b, step));
+        const p = try step.installFile(install_artifact.emitted_implib.?, full_implib_path);
         all_cached = all_cached and p == .fresh;
     }
 
     if (install_artifact.pdb_dir) |pdb_dir| {
-        const src_path = install_artifact.emitted_pdb.?.getPath3(b, step);
-        const full_pdb_path = b.getInstallPath(pdb_dir, fs.path.basename(src_path.sub_path));
-        const p = fs.Dir.updateFile(src_path.root_dir.handle, src_path.sub_path, cwd, full_pdb_path, .{}) catch |err| {
-            return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
-                src_path.sub_path, full_pdb_path, @errorName(err),
-            });
-        };
+        const full_pdb_path = b.getInstallPath(pdb_dir, install_artifact.emitted_pdb.?.basename(b, step));
+        const p = try step.installFile(install_artifact.emitted_pdb.?, full_pdb_path);
         all_cached = all_cached and p == .fresh;
     }
 
     if (install_artifact.h_dir) |h_dir| {
         if (install_artifact.emitted_h) |emitted_h| {
-            const src_path = emitted_h.getPath3(b, step);
-            const full_h_path = b.getInstallPath(h_dir, fs.path.basename(src_path.sub_path));
-            const p = fs.Dir.updateFile(src_path.root_dir.handle, src_path.sub_path, cwd, full_h_path, .{}) catch |err| {
-                return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
-                    src_path.sub_path, full_h_path, @errorName(err),
-                });
-            };
+            const full_h_path = b.getInstallPath(h_dir, emitted_h.basename(b, step));
+            const p = try step.installFile(emitted_h, full_h_path);
             all_cached = all_cached and p == .fresh;
         }
 
         for (install_artifact.artifact.installed_headers.items) |installation| switch (installation) {
             .file => |file| {
-                const src_path = file.source.getPath3(b, step);
                 const full_h_path = b.getInstallPath(h_dir, file.dest_rel_path);
-                const p = fs.Dir.updateFile(src_path.root_dir.handle, src_path.sub_path, cwd, full_h_path, .{}) catch |err| {
-                    return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
-                        src_path.sub_path, full_h_path, @errorName(err),
-                    });
-                };
+                const p = try step.installFile(file.source, full_h_path);
                 all_cached = all_cached and p == .fresh;
             },
             .directory => |dir| {
@@ -209,16 +183,15 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
                         }
                     }
 
-                    const src_entry_path = src_dir_path.join(b.allocator, entry.path) catch @panic("OOM");
                     const full_dest_path = b.pathJoin(&.{ full_h_prefix, entry.path });
                     switch (entry.kind) {
-                        .directory => try cwd.makePath(full_dest_path),
+                        .directory => {
+                            try Step.handleVerbose(b, null, &.{ "install", "-d", full_dest_path });
+                            const p = try step.installDir(full_dest_path);
+                            all_cached = all_cached and p == .existed;
+                        },
                         .file => {
-                            const p = fs.Dir.updateFile(src_entry_path.root_dir.handle, src_entry_path.sub_path, cwd, full_dest_path, .{}) catch |err| {
-                                return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
-                                    src_entry_path.sub_path, full_dest_path, @errorName(err),
-                                });
-                            };
+                            const p = try step.installFile(try dir.source.join(b.allocator, entry.path), full_dest_path);
                             all_cached = all_cached and p == .fresh;
                         },
                         else => continue,
lib/std/Build/Step/InstallDir.zig
@@ -74,31 +74,23 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
     var all_cached = true;
     next_entry: while (try it.next()) |entry| {
         for (install_dir.options.exclude_extensions) |ext| {
-            if (mem.endsWith(u8, entry.path, ext)) {
-                continue :next_entry;
-            }
+            if (mem.endsWith(u8, entry.path, ext)) continue :next_entry;
         }
         if (install_dir.options.include_extensions) |incs| {
-            var found = false;
             for (incs) |inc| {
-                if (mem.endsWith(u8, entry.path, inc)) {
-                    found = true;
-                    break;
-                }
+                if (mem.endsWith(u8, entry.path, inc)) break;
+            } else {
+                continue :next_entry;
             }
-            if (!found) continue :next_entry;
         }
 
-        // relative to src build root
-        const src_sub_path = try src_dir_path.join(arena, entry.path);
+        const src_path = try install_dir.options.source_dir.join(b.allocator, entry.path);
         const dest_path = b.pathJoin(&.{ dest_prefix, entry.path });
-        const cwd = fs.cwd();
-
         switch (entry.kind) {
             .directory => {
-                if (need_derived_inputs) try step.addDirectoryWatchInputFromPath(src_sub_path);
-                try cwd.makePath(dest_path);
-                // TODO: set result_cached=false if the directory did not already exist.
+                if (need_derived_inputs) _ = try step.addDirectoryWatchInput(src_path);
+                const p = try step.installDir(dest_path);
+                all_cached = all_cached and p == .existed;
             },
             .file => {
                 for (install_dir.options.blank_extensions) |ext| {
@@ -108,18 +100,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
                     }
                 }
 
-                const prev_status = fs.Dir.updateFile(
-                    src_sub_path.root_dir.handle,
-                    src_sub_path.sub_path,
-                    cwd,
-                    dest_path,
-                    .{},
-                ) catch |err| {
-                    return step.fail("unable to update file from '{}' to '{s}': {s}", .{
-                        src_sub_path, dest_path, @errorName(err),
-                    });
-                };
-                all_cached = all_cached and prev_status == .fresh;
+                const p = try step.installFile(src_path, dest_path);
+                all_cached = all_cached and p == .fresh;
             },
             else => continue,
         }
lib/std/Build/Step/InstallFile.zig
@@ -41,13 +41,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
     const install_file: *InstallFile = @fieldParentPtr("step", step);
     try step.singleUnchangingWatchInput(install_file.source);
 
-    const full_src_path = install_file.source.getPath2(b, step);
     const full_dest_path = b.getInstallPath(install_file.dir, install_file.dest_rel_path);
-    const cwd = std.fs.cwd();
-    const prev = std.fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| {
-        return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
-            full_src_path, full_dest_path, @errorName(err),
-        });
-    };
-    step.result_cached = prev == .fresh;
+    const p = try step.installFile(install_file.source, full_dest_path);
+    step.result_cached = p == .fresh;
 }
lib/std/Build/Step/ObjCopy.zig
@@ -209,7 +209,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
     }
     if (objcopy.add_section) |section| {
         try argv.append("--add-section");
-        try argv.appendSlice(&.{b.fmt("{s}={s}", .{ section.section_name, section.file_path.getPath(b) })});
+        try argv.appendSlice(&.{b.fmt("{s}={s}", .{ section.section_name, section.file_path.getPath2(b, step) })});
     }
     if (objcopy.set_section_alignment) |set_align| {
         try argv.append("--set-section-alignment");
lib/std/Build/Step/Run.zig
@@ -456,11 +456,28 @@ pub fn addPathDir(run: *Run, search_path: []const u8) void {
     const b = run.step.owner;
     const env_map = getEnvMapInternal(run);
 
-    const key = "PATH";
+    const use_wine = b.enable_wine and b.graph.host.result.os.tag != .windows and use_wine: switch (run.argv.items[0]) {
+        .artifact => |p| p.artifact.rootModuleTarget().os.tag == .windows,
+        .lazy_path => |p| {
+            switch (p.lazy_path) {
+                .generated => |g| if (g.file.step.cast(Step.Compile)) |cs| break :use_wine cs.rootModuleTarget().os.tag == .windows,
+                else => {},
+            }
+            break :use_wine std.mem.endsWith(u8, p.lazy_path.basename(b, &run.step), ".exe");
+        },
+        .decorated_directory => false,
+        .bytes => |bytes| std.mem.endsWith(u8, bytes, ".exe"),
+        .output_file, .output_directory => false,
+    };
+    const key = if (use_wine) "WINEPATH" else "PATH";
     const prev_path = env_map.get(key);
 
     if (prev_path) |pp| {
-        const new_path = b.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path });
+        const new_path = b.fmt("{s}{c}{s}", .{
+            pp,
+            if (use_wine) fs.path.delimiter_windows else fs.path.delimiter,
+            search_path,
+        });
         env_map.put(key, new_path) catch @panic("OOM");
     } else {
         env_map.put(key, b.dupePath(search_path)) catch @panic("OOM");
@@ -866,7 +883,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
     try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, prog_node, null);
 
     const dep_file_dir = std.fs.cwd();
-    const dep_file_basename = dep_output_file.generated_file.getPath();
+    const dep_file_basename = dep_output_file.generated_file.getPath2(b, step);
     if (has_side_effects)
         try man.addDepFile(dep_file_dir, dep_file_basename)
     else
lib/std/Build/Step.zig
@@ -478,6 +478,29 @@ pub fn evalZigProcess(
     return result;
 }
 
+/// Wrapper around `std.fs.Dir.updateFile` that handles verbose and error output.
+pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u8) !std.fs.Dir.PrevStatus {
+    const b = s.owner;
+    const src_path = src_lazy_path.getPath3(b, s);
+    try handleVerbose(b, null, &.{ "install", "-C", b.fmt("{}", .{src_path}), dest_path });
+    return src_path.root_dir.handle.updateFile(src_path.sub_path, std.fs.cwd(), dest_path, .{}) catch |err| {
+        return s.fail("unable to update file from '{}' to '{s}': {s}", .{
+            src_path, dest_path, @errorName(err),
+        });
+    };
+}
+
+/// Wrapper around `std.fs.Dir.makePathStatus` that handles verbose and error output.
+pub fn installDir(s: *Step, dest_path: []const u8) !std.fs.Dir.MakePathStatus {
+    const b = s.owner;
+    try handleVerbose(b, null, &.{ "install", "-d", dest_path });
+    return std.fs.cwd().makePathStatus(dest_path) catch |err| {
+        return s.fail("unable to create dir '{s}': {s}", .{
+            dest_path, @errorName(err),
+        });
+    };
+}
+
 fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool) !?Path {
     const b = s.owner;
     const arena = b.allocator;
@@ -714,8 +737,44 @@ pub fn allocPrintCmd2(
     opt_env: ?*const std.process.EnvMap,
     argv: []const []const u8,
 ) Allocator.Error![]u8 {
+    const shell = struct {
+        fn escape(writer: anytype, string: []const u8, is_argv0: bool) !void {
+            for (string) |c| {
+                if (switch (c) {
+                    else => true,
+                    '%', '+'...':', '@'...'Z', '_', 'a'...'z' => false,
+                    '=' => is_argv0,
+                }) break;
+            } else return writer.writeAll(string);
+
+            try writer.writeByte('"');
+            for (string) |c| {
+                if (switch (c) {
+                    std.ascii.control_code.nul => break,
+                    '!', '"', '$', '\\', '`' => true,
+                    else => !std.ascii.isPrint(c),
+                }) try writer.writeByte('\\');
+                switch (c) {
+                    std.ascii.control_code.nul => unreachable,
+                    std.ascii.control_code.bel => try writer.writeByte('a'),
+                    std.ascii.control_code.bs => try writer.writeByte('b'),
+                    std.ascii.control_code.ht => try writer.writeByte('t'),
+                    std.ascii.control_code.lf => try writer.writeByte('n'),
+                    std.ascii.control_code.vt => try writer.writeByte('v'),
+                    std.ascii.control_code.ff => try writer.writeByte('f'),
+                    std.ascii.control_code.cr => try writer.writeByte('r'),
+                    std.ascii.control_code.esc => try writer.writeByte('E'),
+                    ' '...'~' => try writer.writeByte(c),
+                    else => try writer.print("{o:0>3}", .{c}),
+                }
+            }
+            try writer.writeByte('"');
+        }
+    };
+
     var buf: std.ArrayListUnmanaged(u8) = .empty;
-    if (opt_cwd) |cwd| try buf.writer(arena).print("cd {s} && ", .{cwd});
+    const writer = buf.writer(arena);
+    if (opt_cwd) |cwd| try writer.print("cd {s} && ", .{cwd});
     if (opt_env) |env| {
         const process_env_map = std.process.getEnvMap(arena) catch std.process.EnvMap.init(arena);
         var it = env.iterator();
@@ -725,11 +784,15 @@ pub fn allocPrintCmd2(
             if (process_env_map.get(key)) |process_value| {
                 if (std.mem.eql(u8, value, process_value)) continue;
             }
-            try buf.writer(arena).print("{s}={s} ", .{ key, value });
+            try writer.print("{s}=", .{key});
+            try shell.escape(writer, value, false);
+            try writer.writeByte(' ');
         }
     }
-    for (argv) |arg| {
-        try buf.writer(arena).print("{s} ", .{arg});
+    try shell.escape(writer, argv[0], true);
+    for (argv[1..]) |arg| {
+        try writer.writeByte(' ');
+        try shell.escape(writer, arg, false);
     }
     return buf.toOwnedSlice(arena);
 }
lib/std/fs/Dir.zig
@@ -1146,6 +1146,7 @@ pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) MakeError!void {
 /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
 /// On WASI, `sub_path` should be encoded as valid UTF-8.
 /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
+/// Fails on an empty path with `error.BadPathName` as that is not a path that can be created.
 ///
 /// Paths containing `..` components are handled differently depending on the platform:
 /// - On Windows, `..` are resolved before the path is passed to NtCreateFile, meaning
@@ -1155,10 +1156,19 @@ pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) MakeError!void {
 ///   meaning a `sub_path` like "first/../second" will create both a `./first`
 ///   and a `./second` directory.
 pub fn makePath(self: Dir, sub_path: []const u8) (MakeError || StatFileError)!void {
+    _ = try self.makePathStatus(sub_path);
+}
+
+pub const MakePathStatus = enum { existed, created };
+/// Same as `makePath` except returns whether the path already existed or was successfully created.
+pub fn makePathStatus(self: Dir, sub_path: []const u8) (MakeError || StatFileError)!MakePathStatus {
     var it = try fs.path.componentIterator(sub_path);
-    var component = it.last() orelse return;
+    var status: MakePathStatus = .existed;
+    var component = it.last() orelse return error.BadPathName;
     while (true) {
-        self.makeDir(component.path) catch |err| switch (err) {
+        if (self.makeDir(component.path)) |_| {
+            status = .created;
+        } else |err| switch (err) {
             error.PathAlreadyExists => {
                 // stat the file and return an error if it's not a directory
                 // this is important because otherwise a dangling symlink
@@ -1177,8 +1187,8 @@ pub fn makePath(self: Dir, sub_path: []const u8) (MakeError || StatFileError)!vo
                 continue;
             },
             else => |e| return e,
-        };
-        component = it.next() orelse return;
+        }
+        component = it.next() orelse return status;
     }
 }
 
lib/std/fs/path.zig
@@ -27,8 +27,8 @@ const fs = std.fs;
 const process = std.process;
 const native_os = builtin.target.os.tag;
 
-pub const sep_windows = '\\';
-pub const sep_posix = '/';
+pub const sep_windows: u8 = '\\';
+pub const sep_posix: u8 = '/';
 pub const sep = switch (native_os) {
     .windows, .uefi => sep_windows,
     else => sep_posix,
@@ -41,8 +41,8 @@ pub const sep_str = switch (native_os) {
     else => sep_str_posix,
 };
 
-pub const delimiter_windows = ';';
-pub const delimiter_posix = ':';
+pub const delimiter_windows: u8 = ';';
+pub const delimiter_posix: u8 = ':';
 pub const delimiter = if (native_os == .windows) delimiter_windows else delimiter_posix;
 
 /// Returns if the given byte is a valid path separator
lib/std/Build.zig
@@ -2456,12 +2456,23 @@ pub const GeneratedFile = struct {
     /// This value must be set in the `fn make()` of the `step` and must not be `null` afterwards.
     path: ?[]const u8 = null,
 
+    /// Deprecated, see `getPath2`.
     pub fn getPath(gen: GeneratedFile) []const u8 {
         return gen.step.owner.pathFromCwd(gen.path orelse std.debug.panic(
             "getPath() was called on a GeneratedFile that wasn't built yet. Is there a missing Step dependency on step '{s}'?",
             .{gen.step.name},
         ));
     }
+
+    pub fn getPath2(gen: GeneratedFile, src_builder: *Build, asking_step: ?*Step) []const u8 {
+        return gen.path orelse {
+            std.debug.lockStdErr();
+            const stderr = std.io.getStdErr();
+            dumpBadGetPathHelp(gen.step, stderr, src_builder, asking_step) catch {};
+            std.debug.unlockStdErr();
+            @panic("misconfigured build script");
+        };
+    }
 };
 
 // dirnameAllowEmpty is a variant of fs.path.dirname
@@ -2712,6 +2723,18 @@ pub const LazyPath = union(enum) {
         }
     }
 
+    pub fn basename(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 {
+        return fs.path.basename(switch (lazy_path) {
+            .src_path => |sp| sp.sub_path,
+            .cwd_relative => |sub_path| sub_path,
+            .generated => |gen| if (gen.sub_path.len > 0)
+                gen.sub_path
+            else
+                gen.file.getPath2(src_builder, asking_step),
+            .dependency => |dep| dep.sub_path,
+        });
+    }
+
     /// Copies the internal strings.
     ///
     /// The `b` parameter is only used for its allocator. All *Build instances
build.zig
@@ -203,6 +203,10 @@ pub fn build(b: *std.Build) !void {
     exe.pie = pie;
     exe.entitlements = entitlements;
 
+    const use_llvm = b.option(bool, "use-llvm", "Use the llvm backend");
+    exe.use_llvm = use_llvm;
+    exe.use_lld = use_llvm;
+
     if (no_bin) {
         b.getInstallStep().dependOn(&exe.step);
     } else {
@@ -214,10 +218,6 @@ pub fn build(b: *std.Build) !void {
 
     test_step.dependOn(&exe.step);
 
-    const use_llvm = b.option(bool, "use-llvm", "Use the llvm backend");
-    exe.use_llvm = use_llvm;
-    exe.use_lld = use_llvm;
-
     const exe_options = b.addOptions();
     exe.root_module.addOptions("build_options", exe_options);