From 16d78bc0c024da307c7ab5f6b94622e6b4b37397 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Fri, 13 Jun 2025 12:01:59 -0400 Subject: [PATCH] Build: add install commands to `--verbose` output --- build.zig | 8 +-- lib/std/Build.zig | 23 +++++++++ lib/std/Build/Step.zig | 71 ++++++++++++++++++++++++-- lib/std/Build/Step/Compile.zig | 1 + lib/std/Build/Step/InstallArtifact.zig | 55 +++++--------------- lib/std/Build/Step/InstallDir.zig | 42 +++++---------- lib/std/Build/Step/InstallFile.zig | 10 +--- lib/std/Build/Step/ObjCopy.zig | 2 +- lib/std/Build/Step/Run.zig | 23 +++++++-- lib/std/fs/Dir.zig | 18 +++++-- lib/std/fs/path.zig | 8 +-- 11 files changed, 162 insertions(+), 99 deletions(-) diff --git a/build.zig b/build.zig index ce1c89aa19..c6c624aaee 100644 --- a/build.zig +++ b/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); diff --git a/lib/std/Build.zig b/lib/std/Build.zig index bac7abdd98..ab064d1aea 100644 --- a/lib/std/Build.zig +++ b/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 diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 9cf0ca475e..9d4802fbbc 100644 --- a/lib/std/Build/Step.zig +++ b/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); } diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index fa84ac5e6e..e10840db75 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/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)) { diff --git a/lib/std/Build/Step/InstallArtifact.zig b/lib/std/Build/Step/InstallArtifact.zig index 9d8cb92bb7..6a5b834cae 100644 --- a/lib/std/Build/Step/InstallArtifact.zig +++ b/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, diff --git a/lib/std/Build/Step/InstallDir.zig b/lib/std/Build/Step/InstallDir.zig index 4d4ff78cfc..ece1184d8f 100644 --- a/lib/std/Build/Step/InstallDir.zig +++ b/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)) { + if (mem.endsWith(u8, entry.path, ext)) continue :next_entry; + } + if (install_dir.options.include_extensions) |incs| { + for (incs) |inc| { + if (mem.endsWith(u8, entry.path, inc)) break; + } else { 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 (!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, } diff --git a/lib/std/Build/Step/InstallFile.zig b/lib/std/Build/Step/InstallFile.zig index fb1c0ffc34..10adb4754d 100644 --- a/lib/std/Build/Step/InstallFile.zig +++ b/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; } diff --git a/lib/std/Build/Step/ObjCopy.zig b/lib/std/Build/Step/ObjCopy.zig index 9b2c2d596f..74f871d2fc 100644 --- a/lib/std/Build/Step/ObjCopy.zig +++ b/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"); diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index eb5ea1c607..af7f0ee1a9 100644 --- a/lib/std/Build/Step/Run.zig +++ b/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 diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index 90c46740ca..5d88a2de15 100644 --- a/lib/std/fs/Dir.zig +++ b/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; } } diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig index b2f95a937f..159eb02564 100644 --- a/lib/std/fs/path.zig +++ b/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