Build: add install commands to --verbose output

This commit is contained in:
Jacob Young 2025-06-13 12:01:59 -04:00
parent df4068cabd
commit 16d78bc0c0
11 changed files with 162 additions and 99 deletions

View File

@ -203,6 +203,10 @@ pub fn build(b: *std.Build) !void {
exe.pie = pie; exe.pie = pie;
exe.entitlements = entitlements; 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) { if (no_bin) {
b.getInstallStep().dependOn(&exe.step); b.getInstallStep().dependOn(&exe.step);
} else { } else {
@ -214,10 +218,6 @@ pub fn build(b: *std.Build) !void {
test_step.dependOn(&exe.step); 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(); const exe_options = b.addOptions();
exe.root_module.addOptions("build_options", exe_options); exe.root_module.addOptions("build_options", exe_options);

View File

@ -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. /// This value must be set in the `fn make()` of the `step` and must not be `null` afterwards.
path: ?[]const u8 = null, path: ?[]const u8 = null,
/// Deprecated, see `getPath2`.
pub fn getPath(gen: GeneratedFile) []const u8 { pub fn getPath(gen: GeneratedFile) []const u8 {
return gen.step.owner.pathFromCwd(gen.path orelse std.debug.panic( 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}'?", "getPath() was called on a GeneratedFile that wasn't built yet. Is there a missing Step dependency on step '{s}'?",
.{gen.step.name}, .{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 // 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. /// Copies the internal strings.
/// ///
/// The `b` parameter is only used for its allocator. All *Build instances /// The `b` parameter is only used for its allocator. All *Build instances

View File

@ -478,6 +478,29 @@ pub fn evalZigProcess(
return result; 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 { fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool) !?Path {
const b = s.owner; const b = s.owner;
const arena = b.allocator; const arena = b.allocator;
@ -714,8 +737,44 @@ pub fn allocPrintCmd2(
opt_env: ?*const std.process.EnvMap, opt_env: ?*const std.process.EnvMap,
argv: []const []const u8, argv: []const []const u8,
) Allocator.Error![]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; 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| { if (opt_env) |env| {
const process_env_map = std.process.getEnvMap(arena) catch std.process.EnvMap.init(arena); const process_env_map = std.process.getEnvMap(arena) catch std.process.EnvMap.init(arena);
var it = env.iterator(); var it = env.iterator();
@ -725,11 +784,15 @@ pub fn allocPrintCmd2(
if (process_env_map.get(key)) |process_value| { if (process_env_map.get(key)) |process_value| {
if (std.mem.eql(u8, value, process_value)) continue; 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 shell.escape(writer, argv[0], true);
try buf.writer(arena).print("{s} ", .{arg}); for (argv[1..]) |arg| {
try writer.writeByte(' ');
try shell.escape(writer, arg, false);
} }
return buf.toOwnedSlice(arena); return buf.toOwnedSlice(arena);
} }

View File

@ -668,6 +668,7 @@ pub fn producesPdbFile(compile: *Compile) bool {
else => return false, else => return false,
} }
if (target.ofmt == .c) return false; if (target.ofmt == .c) return false;
if (compile.use_llvm == false) return false;
if (compile.root_module.strip == true or if (compile.root_module.strip == true or
(compile.root_module.strip == null and compile.root_module.optimize == .ReleaseSmall)) (compile.root_module.strip == null and compile.root_module.optimize == .ReleaseSmall))
{ {

View File

@ -119,18 +119,12 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
_ = options; _ = options;
const install_artifact: *InstallArtifact = @fieldParentPtr("step", step); const install_artifact: *InstallArtifact = @fieldParentPtr("step", step);
const b = step.owner; const b = step.owner;
const cwd = fs.cwd();
var all_cached = true; var all_cached = true;
if (install_artifact.dest_dir) |dest_dir| { if (install_artifact.dest_dir) |dest_dir| {
const full_dest_path = b.getInstallPath(dest_dir, install_artifact.dest_sub_path); 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 = try step.installFile(install_artifact.emitted_bin.?, full_dest_path);
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),
});
};
all_cached = all_cached and p == .fresh; all_cached = all_cached and p == .fresh;
if (install_artifact.dylib_symlinks) |dls| { 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| { 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, install_artifact.emitted_implib.?.basename(b, step));
const full_implib_path = b.getInstallPath(implib_dir, fs.path.basename(src_path.sub_path)); const p = try step.installFile(install_artifact.emitted_implib.?, full_implib_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),
});
};
all_cached = all_cached and p == .fresh; all_cached = all_cached and p == .fresh;
} }
if (install_artifact.pdb_dir) |pdb_dir| { 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, install_artifact.emitted_pdb.?.basename(b, step));
const full_pdb_path = b.getInstallPath(pdb_dir, fs.path.basename(src_path.sub_path)); const p = try step.installFile(install_artifact.emitted_pdb.?, full_pdb_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),
});
};
all_cached = all_cached and p == .fresh; all_cached = all_cached and p == .fresh;
} }
if (install_artifact.h_dir) |h_dir| { if (install_artifact.h_dir) |h_dir| {
if (install_artifact.emitted_h) |emitted_h| { if (install_artifact.emitted_h) |emitted_h| {
const src_path = emitted_h.getPath3(b, step); const full_h_path = b.getInstallPath(h_dir, emitted_h.basename(b, step));
const full_h_path = b.getInstallPath(h_dir, fs.path.basename(src_path.sub_path)); const p = try step.installFile(emitted_h, full_h_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),
});
};
all_cached = all_cached and p == .fresh; all_cached = all_cached and p == .fresh;
} }
for (install_artifact.artifact.installed_headers.items) |installation| switch (installation) { for (install_artifact.artifact.installed_headers.items) |installation| switch (installation) {
.file => |file| { .file => |file| {
const src_path = file.source.getPath3(b, step);
const full_h_path = b.getInstallPath(h_dir, file.dest_rel_path); 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| { const p = try step.installFile(file.source, full_h_path);
return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
src_path.sub_path, full_h_path, @errorName(err),
});
};
all_cached = all_cached and p == .fresh; all_cached = all_cached and p == .fresh;
}, },
.directory => |dir| { .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 }); const full_dest_path = b.pathJoin(&.{ full_h_prefix, entry.path });
switch (entry.kind) { 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 => { .file => {
const p = fs.Dir.updateFile(src_entry_path.root_dir.handle, src_entry_path.sub_path, cwd, full_dest_path, .{}) catch |err| { const p = try step.installFile(try dir.source.join(b.allocator, entry.path), full_dest_path);
return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
src_entry_path.sub_path, full_dest_path, @errorName(err),
});
};
all_cached = all_cached and p == .fresh; all_cached = all_cached and p == .fresh;
}, },
else => continue, else => continue,

View File

@ -74,31 +74,23 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
var all_cached = true; var all_cached = true;
next_entry: while (try it.next()) |entry| { next_entry: while (try it.next()) |entry| {
for (install_dir.options.exclude_extensions) |ext| { 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; 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_path = try install_dir.options.source_dir.join(b.allocator, entry.path);
const src_sub_path = try src_dir_path.join(arena, entry.path);
const dest_path = b.pathJoin(&.{ dest_prefix, entry.path }); const dest_path = b.pathJoin(&.{ dest_prefix, entry.path });
const cwd = fs.cwd();
switch (entry.kind) { switch (entry.kind) {
.directory => { .directory => {
if (need_derived_inputs) try step.addDirectoryWatchInputFromPath(src_sub_path); if (need_derived_inputs) _ = try step.addDirectoryWatchInput(src_path);
try cwd.makePath(dest_path); const p = try step.installDir(dest_path);
// TODO: set result_cached=false if the directory did not already exist. all_cached = all_cached and p == .existed;
}, },
.file => { .file => {
for (install_dir.options.blank_extensions) |ext| { 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( const p = try step.installFile(src_path, dest_path);
src_sub_path.root_dir.handle, all_cached = all_cached and p == .fresh;
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;
}, },
else => continue, else => continue,
} }

View File

@ -41,13 +41,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
const install_file: *InstallFile = @fieldParentPtr("step", step); const install_file: *InstallFile = @fieldParentPtr("step", step);
try step.singleUnchangingWatchInput(install_file.source); 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 full_dest_path = b.getInstallPath(install_file.dir, install_file.dest_rel_path);
const cwd = std.fs.cwd(); const p = try step.installFile(install_file.source, full_dest_path);
const prev = std.fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| { step.result_cached = p == .fresh;
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;
} }

View File

@ -209,7 +209,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
} }
if (objcopy.add_section) |section| { if (objcopy.add_section) |section| {
try argv.append("--add-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| { if (objcopy.set_section_alignment) |set_align| {
try argv.append("--set-section-alignment"); try argv.append("--set-section-alignment");

View File

@ -456,11 +456,28 @@ pub fn addPathDir(run: *Run, search_path: []const u8) void {
const b = run.step.owner; const b = run.step.owner;
const env_map = getEnvMapInternal(run); 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); const prev_path = env_map.get(key);
if (prev_path) |pp| { 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"); env_map.put(key, new_path) catch @panic("OOM");
} else { } else {
env_map.put(key, b.dupePath(search_path)) catch @panic("OOM"); 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); 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_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) if (has_side_effects)
try man.addDepFile(dep_file_dir, dep_file_basename) try man.addDepFile(dep_file_dir, dep_file_basename)
else else

View File

@ -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 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 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. /// 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: /// Paths containing `..` components are handled differently depending on the platform:
/// - On Windows, `..` are resolved before the path is passed to NtCreateFile, meaning /// - 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` /// meaning a `sub_path` like "first/../second" will create both a `./first`
/// and a `./second` directory. /// and a `./second` directory.
pub fn makePath(self: Dir, sub_path: []const u8) (MakeError || StatFileError)!void { 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 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) { while (true) {
self.makeDir(component.path) catch |err| switch (err) { if (self.makeDir(component.path)) |_| {
status = .created;
} else |err| switch (err) {
error.PathAlreadyExists => { error.PathAlreadyExists => {
// stat the file and return an error if it's not a directory // stat the file and return an error if it's not a directory
// this is important because otherwise a dangling symlink // 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; continue;
}, },
else => |e| return e, else => |e| return e,
}; }
component = it.next() orelse return; component = it.next() orelse return status;
} }
} }

View File

@ -27,8 +27,8 @@ const fs = std.fs;
const process = std.process; const process = std.process;
const native_os = builtin.target.os.tag; const native_os = builtin.target.os.tag;
pub const sep_windows = '\\'; pub const sep_windows: u8 = '\\';
pub const sep_posix = '/'; pub const sep_posix: u8 = '/';
pub const sep = switch (native_os) { pub const sep = switch (native_os) {
.windows, .uefi => sep_windows, .windows, .uefi => sep_windows,
else => sep_posix, else => sep_posix,
@ -41,8 +41,8 @@ pub const sep_str = switch (native_os) {
else => sep_str_posix, else => sep_str_posix,
}; };
pub const delimiter_windows = ';'; pub const delimiter_windows: u8 = ';';
pub const delimiter_posix = ':'; pub const delimiter_posix: u8 = ':';
pub const delimiter = if (native_os == .windows) delimiter_windows else delimiter_posix; pub const delimiter = if (native_os == .windows) delimiter_windows else delimiter_posix;
/// Returns if the given byte is a valid path separator /// Returns if the given byte is a valid path separator