diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2023-03-02 22:38:07 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2023-03-15 10:48:13 -0700 |
| commit | dcec4d55e36f48e459f4e8f218b8619d9be925db (patch) | |
| tree | 0064e09c25715650b4e1ac641d5a33ec91245be1 /lib/std | |
| parent | 9bf63b09963ca6ea1179dfaa9142498556bfac9d (diff) | |
| download | zig-dcec4d55e36f48e459f4e8f218b8619d9be925db.tar.gz zig-dcec4d55e36f48e459f4e8f218b8619d9be925db.zip | |
eliminate stderr usage in std.Build make() functions
* Eliminate all uses of `std.debug.print` in make() functions, instead
properly using the step failure reporting mechanism.
* Introduce the concept of skipped build steps. These do not cause the
build to fail, and they do allow their dependants to run.
* RunStep gains a new flag, `skip_foreign_checks` which causes the
RunStep to be skipped if stdio mode is `check` and the binary cannot
be executed due to it being a foreign executable.
- RunStep is improved to automatically use known interpreters to
execute binaries if possible (integrating with flags such as
-fqemu and -fwasmtime). It only does this after attempting a native
execution and receiving a "exec file format" error.
- Update RunStep to use an ArrayList for the checks rather than this
ad-hoc reallocation/copying mechanism.
- `expectStdOutEqual` now also implicitly adds an exit_code==0 check
if there is not already an expected termination. This matches
previously expected behavior from older API and can be overridden by
directly setting the checks array.
* Add `dest_sub_path` to `InstallArtifactStep` which allows choosing an
arbitrary subdirectory relative to the prefix, as well as overriding
the basename.
- Delete the custom InstallWithRename step that I found deep in the
test/ directory.
* WriteFileStep will now update its step display name after the first
file is added.
* Add missing stdout checks to various standalone test case build
scripts.
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/Build/CheckFileStep.zig | 7 | ||||
| -rw-r--r-- | lib/std/Build/CheckObjectStep.zig | 138 | ||||
| -rw-r--r-- | lib/std/Build/CompileStep.zig | 3 | ||||
| -rw-r--r-- | lib/std/Build/ConfigHeaderStep.zig | 23 | ||||
| -rw-r--r-- | lib/std/Build/InstallArtifactStep.zig | 8 | ||||
| -rw-r--r-- | lib/std/Build/ObjCopyStep.zig | 3 | ||||
| -rw-r--r-- | lib/std/Build/RunStep.zig | 372 | ||||
| -rw-r--r-- | lib/std/Build/Step.zig | 23 | ||||
| -rw-r--r-- | lib/std/Build/WriteFileStep.zig | 114 | ||||
| -rw-r--r-- | lib/std/child_process.zig | 1 |
10 files changed, 459 insertions, 233 deletions
diff --git a/lib/std/Build/CheckFileStep.zig b/lib/std/Build/CheckFileStep.zig index f70a29840e..03b23d0b03 100644 --- a/lib/std/Build/CheckFileStep.zig +++ b/lib/std/Build/CheckFileStep.zig @@ -42,15 +42,14 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { for (self.expected_matches) |expected_match| { if (mem.indexOf(u8, contents, expected_match) == null) { - std.debug.print( + return step.fail( \\ - \\========= Expected to find: =================== + \\========= expected to find: =================== \\{s} - \\========= But file does not contain it: ======= + \\========= but file does not contain it: ======= \\{s} \\ , .{ expected_match, contents }); - return error.TestFailed; } } } diff --git a/lib/std/Build/CheckObjectStep.zig b/lib/std/Build/CheckObjectStep.zig index 57d280da0e..2a58850fab 100644 --- a/lib/std/Build/CheckObjectStep.zig +++ b/lib/std/Build/CheckObjectStep.zig @@ -133,7 +133,8 @@ const Action = struct { /// Will return true if the `phrase` is correctly parsed into an RPN program and /// its reduced, computed value compares using `op` with the expected value, either /// a literal or another extracted variable. - fn computeCmp(act: Action, gpa: Allocator, global_vars: anytype) !bool { + fn computeCmp(act: Action, step: *Step, global_vars: anytype) !bool { + const gpa = step.owner.allocator; var op_stack = std.ArrayList(enum { add, sub, mod, mul }).init(gpa); var values = std.ArrayList(u64).init(gpa); @@ -150,11 +151,11 @@ const Action = struct { } else { const val = std.fmt.parseInt(u64, next, 0) catch blk: { break :blk global_vars.get(next) orelse { - std.debug.print( + try step.addError( \\ - \\========= Variable was not extracted: =========== + \\========= variable was not extracted: =========== \\{s} - \\ + \\================================================= , .{next}); return error.UnknownVariable; }; @@ -186,11 +187,11 @@ const Action = struct { const exp_value = switch (act.expected.?.value) { .variable => |name| global_vars.get(name) orelse { - std.debug.print( + try step.addError( \\ - \\========= Variable was not extracted: =========== + \\========= variable was not extracted: =========== \\{s} - \\ + \\================================================= , .{name}); return error.UnknownVariable; }, @@ -323,14 +324,12 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { ); const output = switch (self.obj_format) { - .macho => try MachODumper.parseAndDump(contents, .{ - .gpa = gpa, + .macho => try MachODumper.parseAndDump(step, contents, .{ .dump_symtab = self.dump_symtab, }), .elf => @panic("TODO elf parser"), .coff => @panic("TODO coff parser"), - .wasm => try WasmDumper.parseAndDump(contents, .{ - .gpa = gpa, + .wasm => try WasmDumper.parseAndDump(step, contents, .{ .dump_symtab = self.dump_symtab, }), else => unreachable, @@ -346,54 +345,50 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { while (it.next()) |line| { if (try act.match(line, &vars)) break; } else { - std.debug.print( + return step.fail( \\ - \\========= Expected to find: ========================== + \\========= expected to find: ========================== \\{s} - \\========= But parsed file does not contain it: ======= + \\========= but parsed file does not contain it: ======= \\{s} - \\ + \\====================================================== , .{ act.phrase, output }); - return error.TestFailed; } }, .not_present => { while (it.next()) |line| { if (try act.match(line, &vars)) { - std.debug.print( + return step.fail( \\ - \\========= Expected not to find: =================== + \\========= expected not to find: =================== \\{s} - \\========= But parsed file does contain it: ======== + \\========= but parsed file does contain it: ======== \\{s} - \\ + \\=================================================== , .{ act.phrase, output }); - return error.TestFailed; } } }, .compute_cmp => { - const res = act.computeCmp(gpa, vars) catch |err| switch (err) { + const res = act.computeCmp(step, vars) catch |err| switch (err) { error.UnknownVariable => { - std.debug.print( - \\========= From parsed file: ===================== + return step.fail( + \\========= from parsed file: ===================== \\{s} - \\ + \\================================================= , .{output}); - return error.TestFailed; }, else => |e| return e, }; if (!res) { - std.debug.print( + return step.fail( \\ - \\========= Comparison failed for action: =========== + \\========= comparison failed for action: =========== \\{s} {} - \\========= From parsed file: ======================= + \\========= from parsed file: ======================= \\{s} - \\ + \\=================================================== , .{ act.phrase, act.expected.?, output }); - return error.TestFailed; } }, } @@ -402,7 +397,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } const Opts = struct { - gpa: ?Allocator = null, dump_symtab: bool = false, }; @@ -410,8 +404,8 @@ const MachODumper = struct { const LoadCommandIterator = macho.LoadCommandIterator; const symtab_label = "symtab"; - fn parseAndDump(bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 { - const gpa = opts.gpa orelse unreachable; // MachO dumper requires an allocator + fn parseAndDump(step: *Step, bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 { + const gpa = step.owner.allocator; var stream = std.io.fixedBufferStream(bytes); const reader = stream.reader(); @@ -693,8 +687,8 @@ const MachODumper = struct { const WasmDumper = struct { const symtab_label = "symbols"; - fn parseAndDump(bytes: []const u8, opts: Opts) ![]const u8 { - const gpa = opts.gpa orelse unreachable; // Wasm dumper requires an allocator + fn parseAndDump(step: *Step, bytes: []const u8, opts: Opts) ![]const u8 { + const gpa = step.owner.allocator; if (opts.dump_symtab) { @panic("TODO: Implement symbol table parsing and dumping"); } @@ -715,20 +709,24 @@ const WasmDumper = struct { const writer = output.writer(); while (reader.readByte()) |current_byte| { - const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch |err| { - std.debug.print("Found invalid section id '{d}'\n", .{current_byte}); - return err; + const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch { + return step.fail("Found invalid section id '{d}'", .{current_byte}); }; const section_length = try std.leb.readULEB128(u32, reader); - try parseAndDumpSection(section, bytes[fbs.pos..][0..section_length], writer); + try parseAndDumpSection(step, section, bytes[fbs.pos..][0..section_length], writer); fbs.pos += section_length; } else |_| {} // reached end of stream return output.toOwnedSlice(); } - fn parseAndDumpSection(section: std.wasm.Section, data: []const u8, writer: anytype) !void { + fn parseAndDumpSection( + step: *Step, + section: std.wasm.Section, + data: []const u8, + writer: anytype, + ) !void { var fbs = std.io.fixedBufferStream(data); const reader = fbs.reader(); @@ -751,7 +749,7 @@ const WasmDumper = struct { => { const entries = try std.leb.readULEB128(u32, reader); try writer.print("\nentries {d}\n", .{entries}); - try dumpSection(section, data[fbs.pos..], entries, writer); + try dumpSection(step, section, data[fbs.pos..], entries, writer); }, .custom => { const name_length = try std.leb.readULEB128(u32, reader); @@ -760,7 +758,7 @@ const WasmDumper = struct { try writer.print("\nname {s}\n", .{name}); if (mem.eql(u8, name, "name")) { - try parseDumpNames(reader, writer, data); + try parseDumpNames(step, reader, writer, data); } else if (mem.eql(u8, name, "producers")) { try parseDumpProducers(reader, writer, data); } else if (mem.eql(u8, name, "target_features")) { @@ -776,7 +774,7 @@ const WasmDumper = struct { } } - fn dumpSection(section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void { + fn dumpSection(step: *Step, section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void { var fbs = std.io.fixedBufferStream(data); const reader = fbs.reader(); @@ -786,19 +784,18 @@ const WasmDumper = struct { while (i < entries) : (i += 1) { const func_type = try reader.readByte(); if (func_type != std.wasm.function_type) { - std.debug.print("Expected function type, found byte '{d}'\n", .{func_type}); - return error.UnexpectedByte; + return step.fail("expected function type, found byte '{d}'", .{func_type}); } const params = try std.leb.readULEB128(u32, reader); try writer.print("params {d}\n", .{params}); var index: u32 = 0; while (index < params) : (index += 1) { - try parseDumpType(std.wasm.Valtype, reader, writer); + try parseDumpType(step, std.wasm.Valtype, reader, writer); } else index = 0; const returns = try std.leb.readULEB128(u32, reader); try writer.print("returns {d}\n", .{returns}); while (index < returns) : (index += 1) { - try parseDumpType(std.wasm.Valtype, reader, writer); + try parseDumpType(step, std.wasm.Valtype, reader, writer); } } }, @@ -812,9 +809,8 @@ const WasmDumper = struct { const name = data[fbs.pos..][0..name_len]; fbs.pos += name_len; - const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch |err| { - std.debug.print("Invalid import kind\n", .{}); - return err; + const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch { + return step.fail("invalid import kind", .{}); }; try writer.print( @@ -831,11 +827,11 @@ const WasmDumper = struct { try parseDumpLimits(reader, writer); }, .global => { - try parseDumpType(std.wasm.Valtype, reader, writer); + try parseDumpType(step, std.wasm.Valtype, reader, writer); try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u32, reader)}); }, .table => { - try parseDumpType(std.wasm.RefType, reader, writer); + try parseDumpType(step, std.wasm.RefType, reader, writer); try parseDumpLimits(reader, writer); }, } @@ -850,7 +846,7 @@ const WasmDumper = struct { .table => { var i: u32 = 0; while (i < entries) : (i += 1) { - try parseDumpType(std.wasm.RefType, reader, writer); + try parseDumpType(step, std.wasm.RefType, reader, writer); try parseDumpLimits(reader, writer); } }, @@ -863,9 +859,9 @@ const WasmDumper = struct { .global => { var i: u32 = 0; while (i < entries) : (i += 1) { - try parseDumpType(std.wasm.Valtype, reader, writer); + try parseDumpType(step, std.wasm.Valtype, reader, writer); try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u1, reader)}); - try parseDumpInit(reader, writer); + try parseDumpInit(step, reader, writer); } }, .@"export" => { @@ -875,9 +871,8 @@ const WasmDumper = struct { const name = data[fbs.pos..][0..name_len]; fbs.pos += name_len; const kind_byte = try std.leb.readULEB128(u8, reader); - const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch |err| { - std.debug.print("invalid export kind value '{d}'\n", .{kind_byte}); - return err; + const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch { + return step.fail("invalid export kind value '{d}'", .{kind_byte}); }; const index = try std.leb.readULEB128(u32, reader); try writer.print( @@ -892,7 +887,7 @@ const WasmDumper = struct { var i: u32 = 0; while (i < entries) : (i += 1) { try writer.print("table index {d}\n", .{try std.leb.readULEB128(u32, reader)}); - try parseDumpInit(reader, writer); + try parseDumpInit(step, reader, writer); const function_indexes = try std.leb.readULEB128(u32, reader); var function_index: u32 = 0; @@ -908,7 +903,7 @@ const WasmDumper = struct { while (i < entries) : (i += 1) { const index = try std.leb.readULEB128(u32, reader); try writer.print("memory index 0x{x}\n", .{index}); - try parseDumpInit(reader, writer); + try parseDumpInit(step, reader, writer); const size = try std.leb.readULEB128(u32, reader); try writer.print("size {d}\n", .{size}); try reader.skipBytes(size, .{}); // we do not care about the content of the segments @@ -918,11 +913,10 @@ const WasmDumper = struct { } } - fn parseDumpType(comptime WasmType: type, reader: anytype, writer: anytype) !void { + fn parseDumpType(step: *Step, comptime WasmType: type, reader: anytype, writer: anytype) !void { const type_byte = try reader.readByte(); - const valtype = std.meta.intToEnum(WasmType, type_byte) catch |err| { - std.debug.print("Invalid wasm type value '{d}'\n", .{type_byte}); - return err; + const valtype = std.meta.intToEnum(WasmType, type_byte) catch { + return step.fail("Invalid wasm type value '{d}'", .{type_byte}); }; try writer.print("type {s}\n", .{@tagName(valtype)}); } @@ -937,11 +931,10 @@ const WasmDumper = struct { } } - fn parseDumpInit(reader: anytype, writer: anytype) !void { + fn parseDumpInit(step: *Step, reader: anytype, writer: anytype) !void { const byte = try std.leb.readULEB128(u8, reader); - const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch |err| { - std.debug.print("invalid wasm opcode '{d}'\n", .{byte}); - return err; + const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch { + return step.fail("invalid wasm opcode '{d}'", .{byte}); }; switch (opcode) { .i32_const => try writer.print("i32.const {x}\n", .{try std.leb.readILEB128(i32, reader)}), @@ -953,14 +946,13 @@ const WasmDumper = struct { } const end_opcode = try std.leb.readULEB128(u8, reader); if (end_opcode != std.wasm.opcode(.end)) { - std.debug.print("expected 'end' opcode in init expression\n", .{}); - return error.MissingEndOpcode; + return step.fail("expected 'end' opcode in init expression", .{}); } } - fn parseDumpNames(reader: anytype, writer: anytype, data: []const u8) !void { + fn parseDumpNames(step: *Step, reader: anytype, writer: anytype, data: []const u8) !void { while (reader.context.pos < data.len) { - try parseDumpType(std.wasm.NameSubsection, reader, writer); + try parseDumpType(step, std.wasm.NameSubsection, reader, writer); const size = try std.leb.readULEB128(u32, reader); const entries = try std.leb.readULEB128(u32, reader); try writer.print( diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index f1a8f71334..99d99694c3 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -538,8 +538,7 @@ pub fn run(cs: *CompileStep) *RunStep { } pub fn checkObject(self: *CompileStep, obj_format: std.Target.ObjectFormat) *CheckObjectStep { - const b = self.step.owner; - return CheckObjectStep.create(b, self.getOutputSource(), obj_format); + return CheckObjectStep.create(self.step.owner, self.getOutputSource(), obj_format); } pub fn setLinkerScriptPath(self: *CompileStep, source: FileSource) void { diff --git a/lib/std/Build/ConfigHeaderStep.zig b/lib/std/Build/ConfigHeaderStep.zig index 8b4c05bab7..37b04e75a4 100644 --- a/lib/std/Build/ConfigHeaderStep.zig +++ b/lib/std/Build/ConfigHeaderStep.zig @@ -192,13 +192,13 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try output.appendSlice(c_generated_line); const src_path = file_source.getPath(b); const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes); - try render_autoconf(contents, &output, self.values, src_path); + try render_autoconf(step, contents, &output, self.values, src_path); }, .cmake => |file_source| { try output.appendSlice(c_generated_line); const src_path = file_source.getPath(b); const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes); - try render_cmake(contents, &output, self.values, src_path); + try render_cmake(step, contents, &output, self.values, src_path); }, .blank => { try output.appendSlice(c_generated_line); @@ -234,8 +234,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { output_dir; var dir = std.fs.cwd().makeOpenPath(sub_dir_path, .{}) catch |err| { - std.debug.print("unable to make path {s}: {s}\n", .{ output_dir, @errorName(err) }); - return err; + return step.fail("unable to make path '{s}': {s}", .{ output_dir, @errorName(err) }); }; defer dir.close(); @@ -247,6 +246,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } fn render_autoconf( + step: *Step, contents: []const u8, output: *std.ArrayList(u8), values: std.StringArrayHashMap(Value), @@ -273,7 +273,7 @@ fn render_autoconf( } const name = it.rest(); const kv = values_copy.fetchSwapRemove(name) orelse { - std.debug.print("{s}:{d}: error: unspecified config header value: '{s}'\n", .{ + try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{ src_path, line_index + 1, name, }); any_errors = true; @@ -283,15 +283,17 @@ fn render_autoconf( } for (values_copy.keys()) |name| { - std.debug.print("{s}: error: config header value unused: '{s}'\n", .{ src_path, name }); + try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name }); + any_errors = true; } if (any_errors) { - return error.HeaderConfigFailed; + return error.MakeFailed; } } fn render_cmake( + step: *Step, contents: []const u8, output: *std.ArrayList(u8), values: std.StringArrayHashMap(Value), @@ -317,14 +319,14 @@ fn render_cmake( continue; } const name = it.next() orelse { - std.debug.print("{s}:{d}: error: missing define name\n", .{ + try step.addError("{s}:{d}: error: missing define name", .{ src_path, line_index + 1, }); any_errors = true; continue; }; const kv = values_copy.fetchSwapRemove(name) orelse { - std.debug.print("{s}:{d}: error: unspecified config header value: '{s}'\n", .{ + try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{ src_path, line_index + 1, name, }); any_errors = true; @@ -334,7 +336,8 @@ fn render_cmake( } for (values_copy.keys()) |name| { - std.debug.print("{s}: error: config header value unused: '{s}'\n", .{ src_path, name }); + try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name }); + any_errors = true; } if (any_errors) { diff --git a/lib/std/Build/InstallArtifactStep.zig b/lib/std/Build/InstallArtifactStep.zig index 7e35d0a5ee..377cba301c 100644 --- a/lib/std/Build/InstallArtifactStep.zig +++ b/lib/std/Build/InstallArtifactStep.zig @@ -12,6 +12,9 @@ artifact: *CompileStep, dest_dir: InstallDir, pdb_dir: ?InstallDir, h_dir: ?InstallDir, +/// If non-null, adds additional path components relative to dest_dir, and +/// overrides the basename of the CompileStep. +dest_sub_path: ?[]const u8, pub fn create(owner: *std.Build, artifact: *CompileStep) *InstallArtifactStep { if (artifact.install_step) |s| return s; @@ -40,6 +43,7 @@ pub fn create(owner: *std.Build, artifact: *CompileStep) *InstallArtifactStep { } } else null, .h_dir = if (artifact.kind == .lib and artifact.emit_h) .header else null, + .dest_sub_path = null, }; self.step.dependOn(&artifact.step); artifact.install_step = self; @@ -71,7 +75,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const self = @fieldParentPtr(InstallArtifactStep, "step", step); const dest_builder = self.dest_builder; - const full_dest_path = dest_builder.getInstallPath(self.dest_dir, self.artifact.out_filename); + const dest_sub_path = if (self.dest_sub_path) |sub_path| sub_path else self.artifact.out_filename; + const full_dest_path = dest_builder.getInstallPath(self.dest_dir, dest_sub_path); + try src_builder.updateFile( self.artifact.getOutputSource().getPath(src_builder), full_dest_path, diff --git a/lib/std/Build/ObjCopyStep.zig b/lib/std/Build/ObjCopyStep.zig index 7199431ee6..13046b3efe 100644 --- a/lib/std/Build/ObjCopyStep.zig +++ b/lib/std/Build/ObjCopyStep.zig @@ -95,8 +95,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const full_dest_path = try b.cache_root.join(b.allocator, &.{ "o", &digest, self.basename }); const cache_path = "o" ++ fs.path.sep_str ++ digest; b.cache_root.handle.makePath(cache_path) catch |err| { - std.debug.print("unable to make path {s}: {s}\n", .{ cache_path, @errorName(err) }); - return err; + return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) }); }; var argv = std.ArrayList([]const u8).init(b.allocator); diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig index 087483fea8..9304cc758e 100644 --- a/lib/std/Build/RunStep.zig +++ b/lib/std/Build/RunStep.zig @@ -10,6 +10,7 @@ const ArrayList = std.ArrayList; const EnvMap = process.EnvMap; const Allocator = mem.Allocator; const ExecError = std.Build.ExecError; +const assert = std.debug.assert; const RunStep = @This(); @@ -54,6 +55,8 @@ rename_step_with_output_arg: bool = true, /// Command-line arguments such as -fqemu and -fwasmtime may affect whether a /// binary is detected as foreign, as well as system configuration such as /// Rosetta (macOS) and binfmt_misc (Linux). +/// If this RunStep is considered to have side-effects, then this flag does +/// nothing. skip_foreign_checks: bool = false, /// If stderr or stdout exceeds this amount, the child process is killed and @@ -79,7 +82,7 @@ pub const StdIo = union(enum) { /// conditions. /// Note that an explicit check for exit code 0 needs to be added to this /// list if such a check is desireable. - check: []const Check, + check: std.ArrayList(Check), pub const Check = union(enum) { expect_stderr_exact: []const u8, @@ -214,14 +217,20 @@ pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8 env_map.put(b.dupe(key), b.dupe(value)) catch @panic("unhandled error"); } +/// Adds a check for exact stderr match. Does not add any other checks. pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void { const new_check: StdIo.Check = .{ .expect_stderr_exact = self.step.owner.dupe(bytes) }; self.addCheck(new_check); } +/// Adds a check for exact stdout match as well as a check for exit code 0, if +/// there is not already an expected termination check. pub fn expectStdOutEqual(self: *RunStep, bytes: []const u8) void { const new_check: StdIo.Check = .{ .expect_stdout_exact = self.step.owner.dupe(bytes) }; self.addCheck(new_check); + if (!self.hasTermCheck()) { + self.expectExitCode(0); + } } pub fn expectExitCode(self: *RunStep, code: u8) void { @@ -229,19 +238,21 @@ pub fn expectExitCode(self: *RunStep, code: u8) void { self.addCheck(new_check); } +pub fn hasTermCheck(self: RunStep) bool { + for (self.stdio.check.items) |check| switch (check) { + .expect_term => return true, + else => continue, + }; + return false; +} + pub fn addCheck(self: *RunStep, new_check: StdIo.Check) void { - const arena = self.step.owner.allocator; switch (self.stdio) { .infer_from_args => { - const list = arena.create([1]StdIo.Check) catch @panic("OOM"); - list.* = .{new_check}; - self.stdio = .{ .check = list }; - }, - .check => |checks| { - const new_list = arena.alloc(StdIo.Check, checks.len + 1) catch @panic("OOM"); - std.mem.copy(StdIo.Check, new_list, checks); - new_list[checks.len] = new_check; + self.stdio = .{ .check = std.ArrayList(StdIo.Check).init(self.step.owner.allocator) }; + self.stdio.check.append(new_check) catch @panic("OOM"); }, + .check => |*checks| checks.append(new_check) catch @panic("OOM"), else => @panic("illegal call to addCheck: conflicting helper method calls. Suggest to directly set stdio field of RunStep instead"), } } @@ -298,14 +309,15 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { _ = prog_node; const b = step.owner; + const arena = b.allocator; const self = @fieldParentPtr(RunStep, "step", step); const has_side_effects = self.hasSideEffects(); - var argv_list = ArrayList([]const u8).init(b.allocator); + var argv_list = ArrayList([]const u8).init(arena); var output_placeholders = ArrayList(struct { index: usize, output: Arg.Output, - }).init(b.allocator); + }).init(arena); var man = b.cache.obtain(); defer man.deinit(); @@ -357,7 +369,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const digest = man.final(); for (output_placeholders.items) |placeholder| { placeholder.output.generated_file.path = try b.cache_root.join( - b.allocator, + arena, &.{ "o", &digest, placeholder.output.basename }, ); } @@ -367,30 +379,21 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const digest = man.final(); for (output_placeholders.items) |placeholder| { - const output_path = try b.cache_root.join( - b.allocator, - &.{ "o", &digest, placeholder.output.basename }, - ); - const output_dir = fs.path.dirname(output_path).?; - fs.cwd().makePath(output_dir) catch |err| { - std.debug.print("unable to make path {s}: {s}\n", .{ output_dir, @errorName(err) }); - return err; + const output_components = .{ "o", &digest, placeholder.output.basename }; + const output_sub_path = try fs.path.join(arena, &output_components); + const output_sub_dir_path = fs.path.dirname(output_sub_path).?; + b.cache_root.handle.makePath(output_sub_dir_path) catch |err| { + return step.fail("unable to make path '{}{s}': {s}", .{ + b.cache_root, output_sub_dir_path, @errorName(err), + }); }; - + const output_path = try b.cache_root.join(arena, &output_components); placeholder.output.generated_file.path = output_path; argv_list.items[placeholder.index] = output_path; } } - try runCommand( - step, - self.cwd, - argv_list.items, - self.env_map, - self.stdio, - has_side_effects, - self.max_stdio_size, - ); + try runCommand(self, argv_list.items, has_side_effects); if (!has_side_effects) { try man.writeManifest(); @@ -442,92 +445,150 @@ fn termMatches(expected: ?std.ChildProcess.Term, actual: std.ChildProcess.Term) }; } -fn runCommand( - step: *Step, - opt_cwd: ?[]const u8, - argv: []const []const u8, - env_map: ?*EnvMap, - stdio: StdIo, - has_side_effects: bool, - max_stdio_size: usize, -) !void { +fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool) !void { + const step = &self.step; const b = step.owner; const arena = b.allocator; - const cwd = if (opt_cwd) |cwd| b.pathFromRoot(cwd) else b.build_root.path; - try step.handleChildProcUnsupported(opt_cwd, argv); - try Step.handleVerbose(step.owner, opt_cwd, argv); - - var child = std.ChildProcess.init(argv, arena); - child.cwd = cwd; - child.env_map = env_map orelse b.env_map; - - child.stdin_behavior = switch (stdio) { - .infer_from_args => if (has_side_effects) .Inherit else .Ignore, - .inherit => .Inherit, - .check => .Close, - }; - child.stdout_behavior = switch (stdio) { - .infer_from_args => if (has_side_effects) .Inherit else .Ignore, - .inherit => .Inherit, - .check => |checks| if (checksContainStdout(checks)) .Pipe else .Ignore, - }; - child.stderr_behavior = switch (stdio) { - .infer_from_args => if (has_side_effects) .Inherit else .Pipe, - .inherit => .Inherit, - .check => .Pipe, - }; - - child.spawn() catch |err| return step.fail("unable to spawn {s}: {s}", .{ - argv[0], @errorName(err), - }); + try step.handleChildProcUnsupported(self.cwd, argv); + try Step.handleVerbose(step.owner, self.cwd, argv); var stdout_bytes: ?[]const u8 = null; var stderr_bytes: ?[]const u8 = null; - if (child.stdout) |stdout| { - if (child.stderr) |stderr| { - var poller = std.io.poll(arena, enum { stdout, stderr }, .{ - .stdout = stdout, - .stderr = stderr, - }); - defer poller.deinit(); + const term = spawnChildAndCollect(self, argv, &stdout_bytes, &stderr_bytes, has_side_effects) catch |err| term: { + if (err == error.InvalidExe) interpret: { + // TODO: learn the target from the binary directly rather than from + // relying on it being a CompileStep. This will make this logic + // work even for the edge case that the binary was produced by a + // third party. + const exe = switch (self.argv.items[0]) { + .artifact => |exe| exe, + else => break :interpret, + }; + if (exe.kind != .exe) break :interpret; + + var interp_argv = std.ArrayList([]const u8).init(b.allocator); + defer interp_argv.deinit(); + + const need_cross_glibc = exe.target.isGnuLibC() and exe.is_linking_libc; + switch (b.host.getExternalExecutor(exe.target_info, .{ + .qemu_fixes_dl = need_cross_glibc and b.glibc_runtimes_dir != null, + .link_libc = exe.is_linking_libc, + })) { + .native, .rosetta => { + if (self.stdio == .check and self.skip_foreign_checks) + return error.MakeSkipped; + + break :interpret; + }, + .wine => |bin_name| { + if (b.enable_wine) { + try interp_argv.append(bin_name); + } else { + return failForeign(self, "-fwine", argv[0], exe); + } + }, + .qemu => |bin_name| { + if (b.enable_qemu) { + const glibc_dir_arg = if (need_cross_glibc) + b.glibc_runtimes_dir orelse return + else + null; + + try interp_argv.append(bin_name); + + if (glibc_dir_arg) |dir| { + // TODO look into making this a call to `linuxTriple`. This + // needs the directory to be called "i686" rather than + // "x86" which is why we do it manually here. + const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}"; + const cpu_arch = exe.target.getCpuArch(); + const os_tag = exe.target.getOsTag(); + const abi = exe.target.getAbi(); + const cpu_arch_name: []const u8 = if (cpu_arch == .x86) + "i686" + else + @tagName(cpu_arch); + const full_dir = try std.fmt.allocPrint(b.allocator, fmt_str, .{ + dir, cpu_arch_name, @tagName(os_tag), @tagName(abi), + }); + + try interp_argv.append("-L"); + try interp_argv.append(full_dir); + } + } else { + return failForeign(self, "-fqemu", argv[0], exe); + } + }, + .darling => |bin_name| { + if (b.enable_darling) { + try interp_argv.append(bin_name); + } else { + return failForeign(self, "-fdarling", argv[0], exe); + } + }, + .wasmtime => |bin_name| { + if (b.enable_wasmtime) { + try interp_argv.append(bin_name); + try interp_argv.append("--dir=."); + } else { + return failForeign(self, "-fwasmtime", argv[0], exe); + } + }, + .bad_dl => |foreign_dl| { + if (self.stdio == .check and self.skip_foreign_checks) + return error.MakeSkipped; + + const host_dl = b.host.dynamic_linker.get() orelse "(none)"; - while (try poller.poll()) { - if (poller.fifo(.stdout).count > max_stdio_size) - return error.StdoutStreamTooLong; - if (poller.fifo(.stderr).count > max_stdio_size) - return error.StderrStreamTooLong; + return step.fail( + \\the host system is unable to execute binaries from the target + \\ because the host dynamic linker is '{s}', + \\ while the target dynamic linker is '{s}'. + \\ consider setting the dynamic linker or enabling skip_foreign_checks in the Run step + , .{ host_dl, foreign_dl }); + }, + .bad_os_or_cpu => { + if (self.stdio == .check and self.skip_foreign_checks) + return error.MakeSkipped; + + const host_name = try b.host.target.zigTriple(b.allocator); + const foreign_name = try exe.target.zigTriple(b.allocator); + + return step.fail("the host system ({s}) is unable to execute binaries from the target ({s})", .{ + host_name, foreign_name, + }); + }, } - stdout_bytes = try poller.fifo(.stdout).toOwnedSlice(); - stderr_bytes = try poller.fifo(.stderr).toOwnedSlice(); - } else { - stdout_bytes = try stdout.reader().readAllAlloc(arena, max_stdio_size); - } - } else if (child.stderr) |stderr| { - stderr_bytes = try stderr.reader().readAllAlloc(arena, max_stdio_size); - } + if (exe.target.isWindows()) { + // On Windows we don't have rpaths so we have to add .dll search paths to PATH + RunStep.addPathForDynLibsInternal(&self.step, b, exe); + } - if (stderr_bytes) |stderr| if (stderr.len > 0) { - const stderr_is_diagnostic = switch (stdio) { - .check => |checks| !checksContainStderr(checks), - else => true, - }; - if (stderr_is_diagnostic) { - try step.result_error_msgs.append(arena, stderr); + try interp_argv.append(argv[0]); + + try Step.handleVerbose(step.owner, self.cwd, interp_argv.items); + + assert(stdout_bytes == null); + assert(stderr_bytes == null); + break :term spawnChildAndCollect(self, interp_argv.items, &stdout_bytes, &stderr_bytes, has_side_effects) catch |inner_err| { + return step.fail("unable to spawn {s}: {s}", .{ + interp_argv.items[0], @errorName(inner_err), + }); + }; } - }; - const term = child.wait() catch |err| { - return step.fail("unable to wait for {s}: {s}", .{ argv[0], @errorName(err) }); + return step.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err) }); }; - switch (stdio) { - .check => |checks| for (checks) |check| switch (check) { + switch (self.stdio) { + .check => |checks| for (checks.items) |check| switch (check) { .expect_stderr_exact => |expected_bytes| { if (!mem.eql(u8, expected_bytes, stderr_bytes.?)) { return step.fail( + \\ \\========= expected this stderr: ========= \\{s} \\========= but found: ==================== @@ -537,13 +598,14 @@ fn runCommand( , .{ expected_bytes, stderr_bytes.?, - try Step.allocPrintCmd(arena, opt_cwd, argv), + try Step.allocPrintCmd(arena, self.cwd, argv), }); } }, .expect_stderr_match => |match| { if (mem.indexOf(u8, stderr_bytes.?, match) == null) { return step.fail( + \\ \\========= expected to find in stderr: ========= \\{s} \\========= but stderr does not contain it: ===== @@ -553,13 +615,14 @@ fn runCommand( , .{ match, stderr_bytes.?, - try Step.allocPrintCmd(arena, opt_cwd, argv), + try Step.allocPrintCmd(arena, self.cwd, argv), }); } }, .expect_stdout_exact => |expected_bytes| { if (!mem.eql(u8, expected_bytes, stdout_bytes.?)) { return step.fail( + \\ \\========= expected this stdout: ========= \\{s} \\========= but found: ==================== @@ -569,13 +632,14 @@ fn runCommand( , .{ expected_bytes, stdout_bytes.?, - try Step.allocPrintCmd(arena, opt_cwd, argv), + try Step.allocPrintCmd(arena, self.cwd, argv), }); } }, .expect_stdout_match => |match| { if (mem.indexOf(u8, stdout_bytes.?, match) == null) { return step.fail( + \\ \\========= expected to find in stdout: ========= \\{s} \\========= but stdout does not contain it: ===== @@ -585,7 +649,7 @@ fn runCommand( , .{ match, stdout_bytes.?, - try Step.allocPrintCmd(arena, opt_cwd, argv), + try Step.allocPrintCmd(arena, self.cwd, argv), }); } }, @@ -594,17 +658,89 @@ fn runCommand( return step.fail("the following command {} (expected {}):\n{s}", .{ fmtTerm(term), fmtTerm(expected_term), - try Step.allocPrintCmd(arena, opt_cwd, argv), + try Step.allocPrintCmd(arena, self.cwd, argv), }); } }, }, else => { - try step.handleChildProcessTerm(term, opt_cwd, argv); + try step.handleChildProcessTerm(term, self.cwd, argv); }, } } +fn spawnChildAndCollect( + self: *RunStep, + argv: []const []const u8, + stdout_bytes: *?[]const u8, + stderr_bytes: *?[]const u8, + has_side_effects: bool, +) !std.ChildProcess.Term { + const b = self.step.owner; + const arena = b.allocator; + const cwd = if (self.cwd) |cwd| b.pathFromRoot(cwd) else b.build_root.path; + + var child = std.ChildProcess.init(argv, arena); + child.cwd = cwd; + child.env_map = self.env_map orelse b.env_map; + + child.stdin_behavior = switch (self.stdio) { + .infer_from_args => if (has_side_effects) .Inherit else .Ignore, + .inherit => .Inherit, + .check => .Close, + }; + child.stdout_behavior = switch (self.stdio) { + .infer_from_args => if (has_side_effects) .Inherit else .Ignore, + .inherit => .Inherit, + .check => |checks| if (checksContainStdout(checks.items)) .Pipe else .Ignore, + }; + child.stderr_behavior = switch (self.stdio) { + .infer_from_args => if (has_side_effects) .Inherit else .Pipe, + .inherit => .Inherit, + .check => .Pipe, + }; + + child.spawn() catch |err| return self.step.fail("unable to spawn {s}: {s}", .{ + argv[0], @errorName(err), + }); + + if (child.stdout) |stdout| { + if (child.stderr) |stderr| { + var poller = std.io.poll(arena, enum { stdout, stderr }, .{ + .stdout = stdout, + .stderr = stderr, + }); + defer poller.deinit(); + + while (try poller.poll()) { + if (poller.fifo(.stdout).count > self.max_stdio_size) + return error.StdoutStreamTooLong; + if (poller.fifo(.stderr).count > self.max_stdio_size) + return error.StderrStreamTooLong; + } + + stdout_bytes.* = try poller.fifo(.stdout).toOwnedSlice(); + stderr_bytes.* = try poller.fifo(.stderr).toOwnedSlice(); + } else { + stdout_bytes.* = try stdout.reader().readAllAlloc(arena, self.max_stdio_size); + } + } else if (child.stderr) |stderr| { + stderr_bytes.* = try stderr.reader().readAllAlloc(arena, self.max_stdio_size); + } + + if (stderr_bytes.*) |stderr| if (stderr.len > 0) { + const stderr_is_diagnostic = switch (self.stdio) { + .check => |checks| !checksContainStderr(checks.items), + else => true, + }; + if (stderr_is_diagnostic) { + try self.step.result_error_msgs.append(arena, stderr); + } + }; + + return child.wait(); +} + fn addPathForDynLibs(self: *RunStep, artifact: *CompileStep) void { addPathForDynLibsInternal(&self.step, self.step.owner, artifact); } @@ -624,3 +760,29 @@ pub fn addPathForDynLibsInternal(step: *Step, builder: *std.Build, artifact: *Co } } } + +fn failForeign( + self: *RunStep, + suggested_flag: []const u8, + argv0: []const u8, + exe: *CompileStep, +) error{ MakeFailed, MakeSkipped, OutOfMemory } { + switch (self.stdio) { + .check => { + if (self.skip_foreign_checks) + return error.MakeSkipped; + + const b = self.step.owner; + const host_name = try b.host.target.zigTriple(b.allocator); + const foreign_name = try exe.target.zigTriple(b.allocator); + + return self.step.fail( + \\unable to spawn foreign binary '{s}' ({s}) on host system ({s}) + \\ consider using {s} or enabling skip_foreign_checks in the Run step + , .{ argv0, foreign_name, host_name, suggested_flag }); + }, + else => { + return self.step.fail("unable to spawn foreign binary '{s}'", .{argv0}); + }, + } +} diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 3219588050..928e7e8735 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -26,6 +26,9 @@ pub const State = enum { dependency_failure, success, failure, + /// This state indicates that the step did not complete, however, it also did not fail, + /// and it is safe to continue executing its dependencies. + skipped, }; pub const Id = enum { @@ -106,13 +109,15 @@ pub fn init(options: Options) Step { /// If the Step's `make` function reports `error.MakeFailed`, it indicates they /// have already reported the error. Otherwise, we add a simple error report /// here. -pub fn make(s: *Step, prog_node: *std.Progress.Node) error{MakeFailed}!void { - return s.makeFn(s, prog_node) catch |err| { - if (err != error.MakeFailed) { +pub fn make(s: *Step, prog_node: *std.Progress.Node) error{ MakeFailed, MakeSkipped }!void { + return s.makeFn(s, prog_node) catch |err| switch (err) { + error.MakeFailed => return error.MakeFailed, + error.MakeSkipped => return error.MakeSkipped, + else => { const gpa = s.dependencies.allocator; s.result_error_msgs.append(gpa, @errorName(err)) catch @panic("OOM"); - } - return error.MakeFailed; + return error.MakeFailed; + }, }; } @@ -192,10 +197,14 @@ pub fn evalChildProcess(s: *Step, argv: []const []const u8) !void { } pub fn fail(step: *Step, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, MakeFailed } { + try step.addError(fmt, args); + return error.MakeFailed; +} + +pub fn addError(step: *Step, comptime fmt: []const u8, args: anytype) error{OutOfMemory}!void { const arena = step.owner.allocator; const msg = try std.fmt.allocPrint(arena, fmt, args); try step.result_error_msgs.append(arena, msg); - return error.MakeFailed; } /// Assumes that argv contains `--listen=-` and that the process being spawned @@ -398,5 +407,5 @@ fn failWithCacheError(s: *Step, man: *const std.Build.Cache.Manifest, err: anyer const i = man.failed_file_index orelse return err; const pp = man.files.items[i].prefixed_path orelse return err; const prefix = man.cache.prefixes()[pp.prefix].path orelse ""; - return s.fail("{s}: {s}/{s}\n", .{ @errorName(err), prefix, pp.sub_path }); + return s.fail("{s}: {s}/{s}", .{ @errorName(err), prefix, pp.sub_path }); } diff --git a/lib/std/Build/WriteFileStep.zig b/lib/std/Build/WriteFileStep.zig index d554d6efc1..b5164d5730 100644 --- a/lib/std/Build/WriteFileStep.zig +++ b/lib/std/Build/WriteFileStep.zig @@ -37,7 +37,7 @@ pub fn init(owner: *std.Build) WriteFileStep { return .{ .step = Step.init(.{ .id = .write_file, - .name = "writefile", + .name = "WriteFile", .owner = owner, .makeFn = make, }), @@ -56,6 +56,8 @@ pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void { .contents = .{ .bytes = b.dupe(bytes) }, }; wf.files.append(gpa, file) catch @panic("OOM"); + + wf.maybeUpdateName(); } /// Place the file into the generated directory within the local cache, @@ -75,6 +77,8 @@ pub fn addCopyFile(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: [ .contents = .{ .copy = source }, }; wf.files.append(gpa, file) catch @panic("OOM"); + + wf.maybeUpdateName(); } /// A path relative to the package root. @@ -101,6 +105,15 @@ pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSo return null; } +fn maybeUpdateName(wf: *WriteFileStep) void { + if (wf.files.items.len == 1) { + // First time adding a file; update name. + if (std.mem.eql(u8, wf.step.name, "WriteFile")) { + wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wf.files.items[0].sub_path}); + } + } +} + fn make(step: *Step, prog_node: *std.Progress.Node) !void { _ = prog_node; const b = step.owner; @@ -110,14 +123,39 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { // WriteFileStep - arguably it should be a different step. But anyway here // it is, it happens unconditionally and does not interact with the other // files here. + var any_miss = false; for (wf.output_source_files.items) |output_source_file| { - const basename = fs.path.basename(output_source_file.sub_path); if (fs.path.dirname(output_source_file.sub_path)) |dirname| { - var dir = try b.build_root.handle.makeOpenPath(dirname, .{}); - defer dir.close(); - try writeFile(wf, dir, output_source_file.contents, basename); - } else { - try writeFile(wf, b.build_root.handle, output_source_file.contents, basename); + b.build_root.handle.makePath(dirname) catch |err| { + return step.fail("unable to make path '{}{s}': {s}", .{ + b.build_root, dirname, @errorName(err), + }); + }; + } + switch (output_source_file.contents) { + .bytes => |bytes| { + b.build_root.handle.writeFile(output_source_file.sub_path, bytes) catch |err| { + return step.fail("unable to write file '{}{s}': {s}", .{ + b.build_root, output_source_file.sub_path, @errorName(err), + }); + }; + any_miss = true; + }, + .copy => |file_source| { + const source_path = file_source.getPath(b); + const prev_status = fs.Dir.updateFile( + fs.cwd(), + source_path, + b.build_root.handle, + output_source_file.sub_path, + .{}, + ) catch |err| { + return step.fail("unable to update file from '{s}' to '{}{s}': {s}", .{ + source_path, b.build_root, output_source_file.sub_path, @errorName(err), + }); + }; + any_miss = any_miss or prev_status == .stale; + }, } } @@ -164,19 +202,52 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const cache_path = "o" ++ fs.path.sep_str ++ digest; var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| { - std.debug.print("unable to make path {s}: {s}\n", .{ cache_path, @errorName(err) }); - return err; + return step.fail("unable to make path '{}{s}': {s}", .{ + b.cache_root, cache_path, @errorName(err), + }); }; defer cache_dir.close(); for (wf.files.items) |file| { - const basename = fs.path.basename(file.sub_path); if (fs.path.dirname(file.sub_path)) |dirname| { - var dir = try b.cache_root.handle.makeOpenPath(dirname, .{}); - defer dir.close(); - try writeFile(wf, dir, file.contents, basename); - } else { - try writeFile(wf, cache_dir, file.contents, basename); + cache_dir.makePath(dirname) catch |err| { + return step.fail("unable to make path '{}{s}{c}{s}': {s}", .{ + b.cache_root, cache_path, fs.path.sep, dirname, @errorName(err), + }); + }; + } + switch (file.contents) { + .bytes => |bytes| { + cache_dir.writeFile(file.sub_path, bytes) catch |err| { + return step.fail("unable to write file '{}{s}{c}{s}': {s}", .{ + b.cache_root, cache_path, fs.path.sep, file.sub_path, @errorName(err), + }); + }; + }, + .copy => |file_source| { + const source_path = file_source.getPath(b); + const prev_status = fs.Dir.updateFile( + fs.cwd(), + source_path, + cache_dir, + file.sub_path, + .{}, + ) catch |err| { + return step.fail("unable to update file from '{s}' to '{}{s}{c}{s}': {s}", .{ + source_path, + b.cache_root, + cache_path, + fs.path.sep, + file.sub_path, + @errorName(err), + }); + }; + // At this point we already will mark the step as a cache miss. + // But this is kind of a partial cache hit since individual + // file copies may be avoided. Oh well, this information is + // discarded. + _ = prev_status; + }, } file.generated_file.path = try b.cache_root.join( @@ -188,19 +259,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try man.writeManifest(); } -fn writeFile(wf: *WriteFileStep, dir: fs.Dir, contents: Contents, basename: []const u8) !void { - const b = wf.step.owner; - // TODO after landing concurrency PR, improve error reporting here - switch (contents) { - .bytes => |bytes| return dir.writeFile(basename, bytes), - .copy => |file_source| { - const source_path = file_source.getPath(b); - const prev_status = try fs.Dir.updateFile(fs.cwd(), source_path, dir, basename, .{}); - _ = prev_status; // TODO logging (affected by open PR regarding concurrency) - }, - } -} - const std = @import("../std.zig"); const Step = std.Build.Step; const fs = std.fs; diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index f8cba85874..3bdef3177a 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -185,7 +185,6 @@ pub const ChildProcess = struct { } /// Blocks until child process terminates and then cleans up all resources. - /// TODO: set the pid to undefined in this function. pub fn wait(self: *ChildProcess) !Term { const term = if (builtin.os.tag == .windows) try self.waitWindows() |
