authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-03-02 22:38:07-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-03-15 10:48:13-07:00
logdcec4d55e36f48e459f4e8f218b8619d9be925db
tree0064e09c25715650b4e1ac641d5a33ec91245be1
parent9bf63b09963ca6ea1179dfaa9142498556bfac9d

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.

20 files changed, 513 insertions(+), 312 deletions(-)

build.zig+1-2
...@@ -385,7 +385,7 @@ pub fn build(b: *std.Build) !void {...@@ -385,7 +385,7 @@ pub fn build(b: *std.Build) !void {
385 const optimization_modes = chosen_opt_modes_buf[0..chosen_mode_index];385 const optimization_modes = chosen_opt_modes_buf[0..chosen_mode_index];
386386
387 const fmt_include_paths = &.{ "doc", "lib", "src", "test", "tools", "build.zig" };387 const fmt_include_paths = &.{ "doc", "lib", "src", "test", "tools", "build.zig" };
388 const fmt_exclude_paths = &.{ "test/cases" };388 const fmt_exclude_paths = &.{"test/cases"};
389 const check_fmt = b.addFmt(.{389 const check_fmt = b.addFmt(.{
390 .paths = fmt_include_paths,390 .paths = fmt_include_paths,
391 .exclude_paths = fmt_exclude_paths,391 .exclude_paths = fmt_exclude_paths,
...@@ -402,7 +402,6 @@ pub fn build(b: *std.Build) !void {...@@ -402,7 +402,6 @@ pub fn build(b: *std.Build) !void {
402 const do_fmt_step = b.step("fmt", "Modify source files in place to have conforming formatting");402 const do_fmt_step = b.step("fmt", "Modify source files in place to have conforming formatting");
403 do_fmt_step.dependOn(&do_fmt.step);403 do_fmt_step.dependOn(&do_fmt.step);
404404
405
406 test_step.dependOn(tests.addPkgTests(405 test_step.dependOn(tests.addPkgTests(
407 b,406 b,
408 test_filter,407 test_filter,
lib/build_runner.zig+23-12
...@@ -357,6 +357,7 @@ fn runStepNames(...@@ -357,6 +357,7 @@ fn runStepNames(
357 }357 }
358358
359 var success_count: usize = 0;359 var success_count: usize = 0;
360 var skipped_count: usize = 0;
360 var failure_count: usize = 0;361 var failure_count: usize = 0;
361 var pending_count: usize = 0;362 var pending_count: usize = 0;
362 var total_compile_errors: usize = 0;363 var total_compile_errors: usize = 0;
...@@ -379,6 +380,7 @@ fn runStepNames(...@@ -379,6 +380,7 @@ fn runStepNames(
379 },380 },
380 .dependency_failure => pending_count += 1,381 .dependency_failure => pending_count += 1,
381 .success => success_count += 1,382 .success => success_count += 1,
383 .skipped => skipped_count += 1,
382 .failure => {384 .failure => {
383 failure_count += 1;385 failure_count += 1;
384 const compile_errors_len = s.result_error_bundle.errorMessageCount();386 const compile_errors_len = s.result_error_bundle.errorMessageCount();
...@@ -395,13 +397,13 @@ fn runStepNames(...@@ -395,13 +397,13 @@ fn runStepNames(
395 if (failure_count == 0 and enable_summary != true) return cleanExit();397 if (failure_count == 0 and enable_summary != true) return cleanExit();
396398
397 if (enable_summary != false) {399 if (enable_summary != false) {
398 const total_count = success_count + failure_count + pending_count;400 const total_count = success_count + failure_count + pending_count + skipped_count;
399 ttyconf.setColor(stderr, .Cyan) catch {};401 ttyconf.setColor(stderr, .Cyan) catch {};
400 stderr.writeAll("Build Summary:") catch {};402 stderr.writeAll("Build Summary:") catch {};
401 ttyconf.setColor(stderr, .Reset) catch {};403 ttyconf.setColor(stderr, .Reset) catch {};
402 stderr.writer().print(" {d}/{d} steps succeeded; {d} failed", .{404 stderr.writer().print(" {d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
403 success_count, total_count, failure_count,405 if (skipped_count > 0) stderr.writer().print("; {d} skipped", .{skipped_count}) catch {};
404 }) catch {};406 if (failure_count > 0) stderr.writer().print("; {d} failed", .{failure_count}) catch {};
405407
406 if (enable_summary == null) {408 if (enable_summary == null) {
407 ttyconf.setColor(stderr, .Dim) catch {};409 ttyconf.setColor(stderr, .Dim) catch {};
...@@ -503,6 +505,12 @@ fn printTreeStep(...@@ -503,6 +505,12 @@ fn printTreeStep(
503 try ttyconf.setColor(stderr, .Reset);505 try ttyconf.setColor(stderr, .Reset);
504 },506 },
505507
508 .skipped => {
509 try ttyconf.setColor(stderr, .Yellow);
510 try stderr.writeAll(" skipped\n");
511 try ttyconf.setColor(stderr, .Reset);
512 },
513
506 .failure => {514 .failure => {
507 try ttyconf.setColor(stderr, .Red);515 try ttyconf.setColor(stderr, .Red);
508 if (s.result_error_bundle.errorMessageCount() > 0) {516 if (s.result_error_bundle.errorMessageCount() > 0) {
...@@ -569,6 +577,7 @@ fn checkForDependencyLoop(...@@ -569,6 +577,7 @@ fn checkForDependencyLoop(
569 .running => unreachable,577 .running => unreachable,
570 .success => unreachable,578 .success => unreachable,
571 .failure => unreachable,579 .failure => unreachable,
580 .skipped => unreachable,
572 }581 }
573}582}
574583
...@@ -587,7 +596,7 @@ fn workerMakeOneStep(...@@ -587,7 +596,7 @@ fn workerMakeOneStep(
587 // queue this step up again when dependencies are met.596 // queue this step up again when dependencies are met.
588 for (s.dependencies.items) |dep| {597 for (s.dependencies.items) |dep| {
589 switch (@atomicLoad(Step.State, &dep.state, .SeqCst)) {598 switch (@atomicLoad(Step.State, &dep.state, .SeqCst)) {
590 .success => continue,599 .success, .skipped => continue,
591 .failure, .dependency_failure => {600 .failure, .dependency_failure => {
592 @atomicStore(Step.State, &s.state, .dependency_failure, .SeqCst);601 @atomicStore(Step.State, &s.state, .dependency_failure, .SeqCst);
593 return;602 return;
...@@ -639,13 +648,15 @@ fn workerMakeOneStep(...@@ -639,13 +648,15 @@ fn workerMakeOneStep(
639 }648 }
640 }649 }
641650
642 make_result catch |err| {651 if (make_result) |_| {
643 assert(err == error.MakeFailed);652 @atomicStore(Step.State, &s.state, .success, .SeqCst);
644 @atomicStore(Step.State, &s.state, .failure, .SeqCst);653 } else |err| switch (err) {
645 return;654 error.MakeFailed => {
646 };655 @atomicStore(Step.State, &s.state, .failure, .SeqCst);
647656 return;
648 @atomicStore(Step.State, &s.state, .success, .SeqCst);657 },
658 error.MakeSkipped => @atomicStore(Step.State, &s.state, .skipped, .SeqCst),
659 }
649660
650 // Successful completion of a step, so we queue up its dependants as well.661 // Successful completion of a step, so we queue up its dependants as well.
651 for (s.dependants.items) |dep| {662 for (s.dependants.items) |dep| {
lib/std/Build/CheckFileStep.zig+3-4
...@@ -42,15 +42,14 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -42,15 +42,14 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
4242
43 for (self.expected_matches) |expected_match| {43 for (self.expected_matches) |expected_match| {
44 if (mem.indexOf(u8, contents, expected_match) == null) {44 if (mem.indexOf(u8, contents, expected_match) == null) {
45 std.debug.print(45 return step.fail(
46 \\46 \\
47 \\========= Expected to find: ===================47 \\========= expected to find: ===================
48 \\{s}48 \\{s}
49 \\========= But file does not contain it: =======49 \\========= but file does not contain it: =======
50 \\{s}50 \\{s}
51 \\51 \\
52 , .{ expected_match, contents });52 , .{ expected_match, contents });
53 return error.TestFailed;
54 }53 }
55 }54 }
56}55}
lib/std/Build/CheckObjectStep.zig+65-73
...@@ -133,7 +133,8 @@ const Action = struct {...@@ -133,7 +133,8 @@ const Action = struct {
133 /// Will return true if the `phrase` is correctly parsed into an RPN program and133 /// Will return true if the `phrase` is correctly parsed into an RPN program and
134 /// its reduced, computed value compares using `op` with the expected value, either134 /// its reduced, computed value compares using `op` with the expected value, either
135 /// a literal or another extracted variable.135 /// a literal or another extracted variable.
136 fn computeCmp(act: Action, gpa: Allocator, global_vars: anytype) !bool {136 fn computeCmp(act: Action, step: *Step, global_vars: anytype) !bool {
137 const gpa = step.owner.allocator;
137 var op_stack = std.ArrayList(enum { add, sub, mod, mul }).init(gpa);138 var op_stack = std.ArrayList(enum { add, sub, mod, mul }).init(gpa);
138 var values = std.ArrayList(u64).init(gpa);139 var values = std.ArrayList(u64).init(gpa);
139140
...@@ -150,11 +151,11 @@ const Action = struct {...@@ -150,11 +151,11 @@ const Action = struct {
150 } else {151 } else {
151 const val = std.fmt.parseInt(u64, next, 0) catch blk: {152 const val = std.fmt.parseInt(u64, next, 0) catch blk: {
152 break :blk global_vars.get(next) orelse {153 break :blk global_vars.get(next) orelse {
153 std.debug.print(154 try step.addError(
154 \\155 \\
155 \\========= Variable was not extracted: ===========156 \\========= variable was not extracted: ===========
156 \\{s}157 \\{s}
157 \\158 \\=================================================
158 , .{next});159 , .{next});
159 return error.UnknownVariable;160 return error.UnknownVariable;
160 };161 };
...@@ -186,11 +187,11 @@ const Action = struct {...@@ -186,11 +187,11 @@ const Action = struct {
186187
187 const exp_value = switch (act.expected.?.value) {188 const exp_value = switch (act.expected.?.value) {
188 .variable => |name| global_vars.get(name) orelse {189 .variable => |name| global_vars.get(name) orelse {
189 std.debug.print(190 try step.addError(
190 \\191 \\
191 \\========= Variable was not extracted: ===========192 \\========= variable was not extracted: ===========
192 \\{s}193 \\{s}
193 \\194 \\=================================================
194 , .{name});195 , .{name});
195 return error.UnknownVariable;196 return error.UnknownVariable;
196 },197 },
...@@ -323,14 +324,12 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -323,14 +324,12 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
323 );324 );
324325
325 const output = switch (self.obj_format) {326 const output = switch (self.obj_format) {
326 .macho => try MachODumper.parseAndDump(contents, .{327 .macho => try MachODumper.parseAndDump(step, contents, .{
327 .gpa = gpa,
328 .dump_symtab = self.dump_symtab,328 .dump_symtab = self.dump_symtab,
329 }),329 }),
330 .elf => @panic("TODO elf parser"),330 .elf => @panic("TODO elf parser"),
331 .coff => @panic("TODO coff parser"),331 .coff => @panic("TODO coff parser"),
332 .wasm => try WasmDumper.parseAndDump(contents, .{332 .wasm => try WasmDumper.parseAndDump(step, contents, .{
333 .gpa = gpa,
334 .dump_symtab = self.dump_symtab,333 .dump_symtab = self.dump_symtab,
335 }),334 }),
336 else => unreachable,335 else => unreachable,
...@@ -346,54 +345,50 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -346,54 +345,50 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
346 while (it.next()) |line| {345 while (it.next()) |line| {
347 if (try act.match(line, &vars)) break;346 if (try act.match(line, &vars)) break;
348 } else {347 } else {
349 std.debug.print(348 return step.fail(
350 \\349 \\
351 \\========= Expected to find: ==========================350 \\========= expected to find: ==========================
352 \\{s}351 \\{s}
353 \\========= But parsed file does not contain it: =======352 \\========= but parsed file does not contain it: =======
354 \\{s}353 \\{s}
355 \\354 \\======================================================
356 , .{ act.phrase, output });355 , .{ act.phrase, output });
357 return error.TestFailed;
358 }356 }
359 },357 },
360 .not_present => {358 .not_present => {
361 while (it.next()) |line| {359 while (it.next()) |line| {
362 if (try act.match(line, &vars)) {360 if (try act.match(line, &vars)) {
363 std.debug.print(361 return step.fail(
364 \\362 \\
365 \\========= Expected not to find: ===================363 \\========= expected not to find: ===================
366 \\{s}364 \\{s}
367 \\========= But parsed file does contain it: ========365 \\========= but parsed file does contain it: ========
368 \\{s}366 \\{s}
369 \\367 \\===================================================
370 , .{ act.phrase, output });368 , .{ act.phrase, output });
371 return error.TestFailed;
372 }369 }
373 }370 }
374 },371 },
375 .compute_cmp => {372 .compute_cmp => {
376 const res = act.computeCmp(gpa, vars) catch |err| switch (err) {373 const res = act.computeCmp(step, vars) catch |err| switch (err) {
377 error.UnknownVariable => {374 error.UnknownVariable => {
378 std.debug.print(375 return step.fail(
379 \\========= From parsed file: =====================376 \\========= from parsed file: =====================
380 \\{s}377 \\{s}
381 \\378 \\=================================================
382 , .{output});379 , .{output});
383 return error.TestFailed;
384 },380 },
385 else => |e| return e,381 else => |e| return e,
386 };382 };
387 if (!res) {383 if (!res) {
388 std.debug.print(384 return step.fail(
389 \\385 \\
390 \\========= Comparison failed for action: ===========386 \\========= comparison failed for action: ===========
391 \\{s} {}387 \\{s} {}
392 \\========= From parsed file: =======================388 \\========= from parsed file: =======================
393 \\{s}389 \\{s}
394 \\390 \\===================================================
395 , .{ act.phrase, act.expected.?, output });391 , .{ act.phrase, act.expected.?, output });
396 return error.TestFailed;
397 }392 }
398 },393 },
399 }394 }
...@@ -402,7 +397,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -402,7 +397,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
402}397}
403398
404const Opts = struct {399const Opts = struct {
405 gpa: ?Allocator = null,
406 dump_symtab: bool = false,400 dump_symtab: bool = false,
407};401};
408402
...@@ -410,8 +404,8 @@ const MachODumper = struct {...@@ -410,8 +404,8 @@ const MachODumper = struct {
410 const LoadCommandIterator = macho.LoadCommandIterator;404 const LoadCommandIterator = macho.LoadCommandIterator;
411 const symtab_label = "symtab";405 const symtab_label = "symtab";
412406
413 fn parseAndDump(bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 {407 fn parseAndDump(step: *Step, bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 {
414 const gpa = opts.gpa orelse unreachable; // MachO dumper requires an allocator408 const gpa = step.owner.allocator;
415 var stream = std.io.fixedBufferStream(bytes);409 var stream = std.io.fixedBufferStream(bytes);
416 const reader = stream.reader();410 const reader = stream.reader();
417411
...@@ -693,8 +687,8 @@ const MachODumper = struct {...@@ -693,8 +687,8 @@ const MachODumper = struct {
693const WasmDumper = struct {687const WasmDumper = struct {
694 const symtab_label = "symbols";688 const symtab_label = "symbols";
695689
696 fn parseAndDump(bytes: []const u8, opts: Opts) ![]const u8 {690 fn parseAndDump(step: *Step, bytes: []const u8, opts: Opts) ![]const u8 {
697 const gpa = opts.gpa orelse unreachable; // Wasm dumper requires an allocator691 const gpa = step.owner.allocator;
698 if (opts.dump_symtab) {692 if (opts.dump_symtab) {
699 @panic("TODO: Implement symbol table parsing and dumping");693 @panic("TODO: Implement symbol table parsing and dumping");
700 }694 }
...@@ -715,20 +709,24 @@ const WasmDumper = struct {...@@ -715,20 +709,24 @@ const WasmDumper = struct {
715 const writer = output.writer();709 const writer = output.writer();
716710
717 while (reader.readByte()) |current_byte| {711 while (reader.readByte()) |current_byte| {
718 const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch |err| {712 const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch {
719 std.debug.print("Found invalid section id '{d}'\n", .{current_byte});713 return step.fail("Found invalid section id '{d}'", .{current_byte});
720 return err;
721 };714 };
722715
723 const section_length = try std.leb.readULEB128(u32, reader);716 const section_length = try std.leb.readULEB128(u32, reader);
724 try parseAndDumpSection(section, bytes[fbs.pos..][0..section_length], writer);717 try parseAndDumpSection(step, section, bytes[fbs.pos..][0..section_length], writer);
725 fbs.pos += section_length;718 fbs.pos += section_length;
726 } else |_| {} // reached end of stream719 } else |_| {} // reached end of stream
727720
728 return output.toOwnedSlice();721 return output.toOwnedSlice();
729 }722 }
730723
731 fn parseAndDumpSection(section: std.wasm.Section, data: []const u8, writer: anytype) !void {724 fn parseAndDumpSection(
725 step: *Step,
726 section: std.wasm.Section,
727 data: []const u8,
728 writer: anytype,
729 ) !void {
732 var fbs = std.io.fixedBufferStream(data);730 var fbs = std.io.fixedBufferStream(data);
733 const reader = fbs.reader();731 const reader = fbs.reader();
734732
...@@ -751,7 +749,7 @@ const WasmDumper = struct {...@@ -751,7 +749,7 @@ const WasmDumper = struct {
751 => {749 => {
752 const entries = try std.leb.readULEB128(u32, reader);750 const entries = try std.leb.readULEB128(u32, reader);
753 try writer.print("\nentries {d}\n", .{entries});751 try writer.print("\nentries {d}\n", .{entries});
754 try dumpSection(section, data[fbs.pos..], entries, writer);752 try dumpSection(step, section, data[fbs.pos..], entries, writer);
755 },753 },
756 .custom => {754 .custom => {
757 const name_length = try std.leb.readULEB128(u32, reader);755 const name_length = try std.leb.readULEB128(u32, reader);
...@@ -760,7 +758,7 @@ const WasmDumper = struct {...@@ -760,7 +758,7 @@ const WasmDumper = struct {
760 try writer.print("\nname {s}\n", .{name});758 try writer.print("\nname {s}\n", .{name});
761759
762 if (mem.eql(u8, name, "name")) {760 if (mem.eql(u8, name, "name")) {
763 try parseDumpNames(reader, writer, data);761 try parseDumpNames(step, reader, writer, data);
764 } else if (mem.eql(u8, name, "producers")) {762 } else if (mem.eql(u8, name, "producers")) {
765 try parseDumpProducers(reader, writer, data);763 try parseDumpProducers(reader, writer, data);
766 } else if (mem.eql(u8, name, "target_features")) {764 } else if (mem.eql(u8, name, "target_features")) {
...@@ -776,7 +774,7 @@ const WasmDumper = struct {...@@ -776,7 +774,7 @@ const WasmDumper = struct {
776 }774 }
777 }775 }
778776
779 fn dumpSection(section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void {777 fn dumpSection(step: *Step, section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void {
780 var fbs = std.io.fixedBufferStream(data);778 var fbs = std.io.fixedBufferStream(data);
781 const reader = fbs.reader();779 const reader = fbs.reader();
782780
...@@ -786,19 +784,18 @@ const WasmDumper = struct {...@@ -786,19 +784,18 @@ const WasmDumper = struct {
786 while (i < entries) : (i += 1) {784 while (i < entries) : (i += 1) {
787 const func_type = try reader.readByte();785 const func_type = try reader.readByte();
788 if (func_type != std.wasm.function_type) {786 if (func_type != std.wasm.function_type) {
789 std.debug.print("Expected function type, found byte '{d}'\n", .{func_type});787 return step.fail("expected function type, found byte '{d}'", .{func_type});
790 return error.UnexpectedByte;
791 }788 }
792 const params = try std.leb.readULEB128(u32, reader);789 const params = try std.leb.readULEB128(u32, reader);
793 try writer.print("params {d}\n", .{params});790 try writer.print("params {d}\n", .{params});
794 var index: u32 = 0;791 var index: u32 = 0;
795 while (index < params) : (index += 1) {792 while (index < params) : (index += 1) {
796 try parseDumpType(std.wasm.Valtype, reader, writer);793 try parseDumpType(step, std.wasm.Valtype, reader, writer);
797 } else index = 0;794 } else index = 0;
798 const returns = try std.leb.readULEB128(u32, reader);795 const returns = try std.leb.readULEB128(u32, reader);
799 try writer.print("returns {d}\n", .{returns});796 try writer.print("returns {d}\n", .{returns});
800 while (index < returns) : (index += 1) {797 while (index < returns) : (index += 1) {
801 try parseDumpType(std.wasm.Valtype, reader, writer);798 try parseDumpType(step, std.wasm.Valtype, reader, writer);
802 }799 }
803 }800 }
804 },801 },
...@@ -812,9 +809,8 @@ const WasmDumper = struct {...@@ -812,9 +809,8 @@ const WasmDumper = struct {
812 const name = data[fbs.pos..][0..name_len];809 const name = data[fbs.pos..][0..name_len];
813 fbs.pos += name_len;810 fbs.pos += name_len;
814811
815 const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch |err| {812 const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch {
816 std.debug.print("Invalid import kind\n", .{});813 return step.fail("invalid import kind", .{});
817 return err;
818 };814 };
819815
820 try writer.print(816 try writer.print(
...@@ -831,11 +827,11 @@ const WasmDumper = struct {...@@ -831,11 +827,11 @@ const WasmDumper = struct {
831 try parseDumpLimits(reader, writer);827 try parseDumpLimits(reader, writer);
832 },828 },
833 .global => {829 .global => {
834 try parseDumpType(std.wasm.Valtype, reader, writer);830 try parseDumpType(step, std.wasm.Valtype, reader, writer);
835 try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u32, reader)});831 try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u32, reader)});
836 },832 },
837 .table => {833 .table => {
838 try parseDumpType(std.wasm.RefType, reader, writer);834 try parseDumpType(step, std.wasm.RefType, reader, writer);
839 try parseDumpLimits(reader, writer);835 try parseDumpLimits(reader, writer);
840 },836 },
841 }837 }
...@@ -850,7 +846,7 @@ const WasmDumper = struct {...@@ -850,7 +846,7 @@ const WasmDumper = struct {
850 .table => {846 .table => {
851 var i: u32 = 0;847 var i: u32 = 0;
852 while (i < entries) : (i += 1) {848 while (i < entries) : (i += 1) {
853 try parseDumpType(std.wasm.RefType, reader, writer);849 try parseDumpType(step, std.wasm.RefType, reader, writer);
854 try parseDumpLimits(reader, writer);850 try parseDumpLimits(reader, writer);
855 }851 }
856 },852 },
...@@ -863,9 +859,9 @@ const WasmDumper = struct {...@@ -863,9 +859,9 @@ const WasmDumper = struct {
863 .global => {859 .global => {
864 var i: u32 = 0;860 var i: u32 = 0;
865 while (i < entries) : (i += 1) {861 while (i < entries) : (i += 1) {
866 try parseDumpType(std.wasm.Valtype, reader, writer);862 try parseDumpType(step, std.wasm.Valtype, reader, writer);
867 try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u1, reader)});863 try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u1, reader)});
868 try parseDumpInit(reader, writer);864 try parseDumpInit(step, reader, writer);
869 }865 }
870 },866 },
871 .@"export" => {867 .@"export" => {
...@@ -875,9 +871,8 @@ const WasmDumper = struct {...@@ -875,9 +871,8 @@ const WasmDumper = struct {
875 const name = data[fbs.pos..][0..name_len];871 const name = data[fbs.pos..][0..name_len];
876 fbs.pos += name_len;872 fbs.pos += name_len;
877 const kind_byte = try std.leb.readULEB128(u8, reader);873 const kind_byte = try std.leb.readULEB128(u8, reader);
878 const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch |err| {874 const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch {
879 std.debug.print("invalid export kind value '{d}'\n", .{kind_byte});875 return step.fail("invalid export kind value '{d}'", .{kind_byte});
880 return err;
881 };876 };
882 const index = try std.leb.readULEB128(u32, reader);877 const index = try std.leb.readULEB128(u32, reader);
883 try writer.print(878 try writer.print(
...@@ -892,7 +887,7 @@ const WasmDumper = struct {...@@ -892,7 +887,7 @@ const WasmDumper = struct {
892 var i: u32 = 0;887 var i: u32 = 0;
893 while (i < entries) : (i += 1) {888 while (i < entries) : (i += 1) {
894 try writer.print("table index {d}\n", .{try std.leb.readULEB128(u32, reader)});889 try writer.print("table index {d}\n", .{try std.leb.readULEB128(u32, reader)});
895 try parseDumpInit(reader, writer);890 try parseDumpInit(step, reader, writer);
896891
897 const function_indexes = try std.leb.readULEB128(u32, reader);892 const function_indexes = try std.leb.readULEB128(u32, reader);
898 var function_index: u32 = 0;893 var function_index: u32 = 0;
...@@ -908,7 +903,7 @@ const WasmDumper = struct {...@@ -908,7 +903,7 @@ const WasmDumper = struct {
908 while (i < entries) : (i += 1) {903 while (i < entries) : (i += 1) {
909 const index = try std.leb.readULEB128(u32, reader);904 const index = try std.leb.readULEB128(u32, reader);
910 try writer.print("memory index 0x{x}\n", .{index});905 try writer.print("memory index 0x{x}\n", .{index});
911 try parseDumpInit(reader, writer);906 try parseDumpInit(step, reader, writer);
912 const size = try std.leb.readULEB128(u32, reader);907 const size = try std.leb.readULEB128(u32, reader);
913 try writer.print("size {d}\n", .{size});908 try writer.print("size {d}\n", .{size});
914 try reader.skipBytes(size, .{}); // we do not care about the content of the segments909 try reader.skipBytes(size, .{}); // we do not care about the content of the segments
...@@ -918,11 +913,10 @@ const WasmDumper = struct {...@@ -918,11 +913,10 @@ const WasmDumper = struct {
918 }913 }
919 }914 }
920915
921 fn parseDumpType(comptime WasmType: type, reader: anytype, writer: anytype) !void {916 fn parseDumpType(step: *Step, comptime WasmType: type, reader: anytype, writer: anytype) !void {
922 const type_byte = try reader.readByte();917 const type_byte = try reader.readByte();
923 const valtype = std.meta.intToEnum(WasmType, type_byte) catch |err| {918 const valtype = std.meta.intToEnum(WasmType, type_byte) catch {
924 std.debug.print("Invalid wasm type value '{d}'\n", .{type_byte});919 return step.fail("Invalid wasm type value '{d}'", .{type_byte});
925 return err;
926 };920 };
927 try writer.print("type {s}\n", .{@tagName(valtype)});921 try writer.print("type {s}\n", .{@tagName(valtype)});
928 }922 }
...@@ -937,11 +931,10 @@ const WasmDumper = struct {...@@ -937,11 +931,10 @@ const WasmDumper = struct {
937 }931 }
938 }932 }
939933
940 fn parseDumpInit(reader: anytype, writer: anytype) !void {934 fn parseDumpInit(step: *Step, reader: anytype, writer: anytype) !void {
941 const byte = try std.leb.readULEB128(u8, reader);935 const byte = try std.leb.readULEB128(u8, reader);
942 const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch |err| {936 const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch {
943 std.debug.print("invalid wasm opcode '{d}'\n", .{byte});937 return step.fail("invalid wasm opcode '{d}'", .{byte});
944 return err;
945 };938 };
946 switch (opcode) {939 switch (opcode) {
947 .i32_const => try writer.print("i32.const {x}\n", .{try std.leb.readILEB128(i32, reader)}),940 .i32_const => try writer.print("i32.const {x}\n", .{try std.leb.readILEB128(i32, reader)}),
...@@ -953,14 +946,13 @@ const WasmDumper = struct {...@@ -953,14 +946,13 @@ const WasmDumper = struct {
953 }946 }
954 const end_opcode = try std.leb.readULEB128(u8, reader);947 const end_opcode = try std.leb.readULEB128(u8, reader);
955 if (end_opcode != std.wasm.opcode(.end)) {948 if (end_opcode != std.wasm.opcode(.end)) {
956 std.debug.print("expected 'end' opcode in init expression\n", .{});949 return step.fail("expected 'end' opcode in init expression", .{});
957 return error.MissingEndOpcode;
958 }950 }
959 }951 }
960952
961 fn parseDumpNames(reader: anytype, writer: anytype, data: []const u8) !void {953 fn parseDumpNames(step: *Step, reader: anytype, writer: anytype, data: []const u8) !void {
962 while (reader.context.pos < data.len) {954 while (reader.context.pos < data.len) {
963 try parseDumpType(std.wasm.NameSubsection, reader, writer);955 try parseDumpType(step, std.wasm.NameSubsection, reader, writer);
964 const size = try std.leb.readULEB128(u32, reader);956 const size = try std.leb.readULEB128(u32, reader);
965 const entries = try std.leb.readULEB128(u32, reader);957 const entries = try std.leb.readULEB128(u32, reader);
966 try writer.print(958 try writer.print(
lib/std/Build/CompileStep.zig+1-2
...@@ -538,8 +538,7 @@ pub fn run(cs: *CompileStep) *RunStep {...@@ -538,8 +538,7 @@ pub fn run(cs: *CompileStep) *RunStep {
538}538}
539539
540pub fn checkObject(self: *CompileStep, obj_format: std.Target.ObjectFormat) *CheckObjectStep {540pub fn checkObject(self: *CompileStep, obj_format: std.Target.ObjectFormat) *CheckObjectStep {
541 const b = self.step.owner;541 return CheckObjectStep.create(self.step.owner, self.getOutputSource(), obj_format);
542 return CheckObjectStep.create(b, self.getOutputSource(), obj_format);
543}542}
544543
545pub fn setLinkerScriptPath(self: *CompileStep, source: FileSource) void {544pub fn setLinkerScriptPath(self: *CompileStep, source: FileSource) void {
lib/std/Build/ConfigHeaderStep.zig+13-10
...@@ -192,13 +192,13 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -192,13 +192,13 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
192 try output.appendSlice(c_generated_line);192 try output.appendSlice(c_generated_line);
193 const src_path = file_source.getPath(b);193 const src_path = file_source.getPath(b);
194 const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes);194 const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes);
195 try render_autoconf(contents, &output, self.values, src_path);195 try render_autoconf(step, contents, &output, self.values, src_path);
196 },196 },
197 .cmake => |file_source| {197 .cmake => |file_source| {
198 try output.appendSlice(c_generated_line);198 try output.appendSlice(c_generated_line);
199 const src_path = file_source.getPath(b);199 const src_path = file_source.getPath(b);
200 const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes);200 const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes);
201 try render_cmake(contents, &output, self.values, src_path);201 try render_cmake(step, contents, &output, self.values, src_path);
202 },202 },
203 .blank => {203 .blank => {
204 try output.appendSlice(c_generated_line);204 try output.appendSlice(c_generated_line);
...@@ -234,8 +234,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -234,8 +234,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
234 output_dir;234 output_dir;
235235
236 var dir = std.fs.cwd().makeOpenPath(sub_dir_path, .{}) catch |err| {236 var dir = std.fs.cwd().makeOpenPath(sub_dir_path, .{}) catch |err| {
237 std.debug.print("unable to make path {s}: {s}\n", .{ output_dir, @errorName(err) });237 return step.fail("unable to make path '{s}': {s}", .{ output_dir, @errorName(err) });
238 return err;
239 };238 };
240 defer dir.close();239 defer dir.close();
241240
...@@ -247,6 +246,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -247,6 +246,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
247}246}
248247
249fn render_autoconf(248fn render_autoconf(
249 step: *Step,
250 contents: []const u8,250 contents: []const u8,
251 output: *std.ArrayList(u8),251 output: *std.ArrayList(u8),
252 values: std.StringArrayHashMap(Value),252 values: std.StringArrayHashMap(Value),
...@@ -273,7 +273,7 @@ fn render_autoconf(...@@ -273,7 +273,7 @@ fn render_autoconf(
273 }273 }
274 const name = it.rest();274 const name = it.rest();
275 const kv = values_copy.fetchSwapRemove(name) orelse {275 const kv = values_copy.fetchSwapRemove(name) orelse {
276 std.debug.print("{s}:{d}: error: unspecified config header value: '{s}'\n", .{276 try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{
277 src_path, line_index + 1, name,277 src_path, line_index + 1, name,
278 });278 });
279 any_errors = true;279 any_errors = true;
...@@ -283,15 +283,17 @@ fn render_autoconf(...@@ -283,15 +283,17 @@ fn render_autoconf(
283 }283 }
284284
285 for (values_copy.keys()) |name| {285 for (values_copy.keys()) |name| {
286 std.debug.print("{s}: error: config header value unused: '{s}'\n", .{ src_path, name });286 try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name });
287 any_errors = true;
287 }288 }
288289
289 if (any_errors) {290 if (any_errors) {
290 return error.HeaderConfigFailed;291 return error.MakeFailed;
291 }292 }
292}293}
293294
294fn render_cmake(295fn render_cmake(
296 step: *Step,
295 contents: []const u8,297 contents: []const u8,
296 output: *std.ArrayList(u8),298 output: *std.ArrayList(u8),
297 values: std.StringArrayHashMap(Value),299 values: std.StringArrayHashMap(Value),
...@@ -317,14 +319,14 @@ fn render_cmake(...@@ -317,14 +319,14 @@ fn render_cmake(
317 continue;319 continue;
318 }320 }
319 const name = it.next() orelse {321 const name = it.next() orelse {
320 std.debug.print("{s}:{d}: error: missing define name\n", .{322 try step.addError("{s}:{d}: error: missing define name", .{
321 src_path, line_index + 1,323 src_path, line_index + 1,
322 });324 });
323 any_errors = true;325 any_errors = true;
324 continue;326 continue;
325 };327 };
326 const kv = values_copy.fetchSwapRemove(name) orelse {328 const kv = values_copy.fetchSwapRemove(name) orelse {
327 std.debug.print("{s}:{d}: error: unspecified config header value: '{s}'\n", .{329 try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{
328 src_path, line_index + 1, name,330 src_path, line_index + 1, name,
329 });331 });
330 any_errors = true;332 any_errors = true;
...@@ -334,7 +336,8 @@ fn render_cmake(...@@ -334,7 +336,8 @@ fn render_cmake(
334 }336 }
335337
336 for (values_copy.keys()) |name| {338 for (values_copy.keys()) |name| {
337 std.debug.print("{s}: error: config header value unused: '{s}'\n", .{ src_path, name });339 try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name });
340 any_errors = true;
338 }341 }
339342
340 if (any_errors) {343 if (any_errors) {
lib/std/Build/InstallArtifactStep.zig+7-1
...@@ -12,6 +12,9 @@ artifact: *CompileStep,...@@ -12,6 +12,9 @@ artifact: *CompileStep,
12dest_dir: InstallDir,12dest_dir: InstallDir,
13pdb_dir: ?InstallDir,13pdb_dir: ?InstallDir,
14h_dir: ?InstallDir,14h_dir: ?InstallDir,
15/// If non-null, adds additional path components relative to dest_dir, and
16/// overrides the basename of the CompileStep.
17dest_sub_path: ?[]const u8,
1518
16pub fn create(owner: *std.Build, artifact: *CompileStep) *InstallArtifactStep {19pub fn create(owner: *std.Build, artifact: *CompileStep) *InstallArtifactStep {
17 if (artifact.install_step) |s| return s;20 if (artifact.install_step) |s| return s;
...@@ -40,6 +43,7 @@ pub fn create(owner: *std.Build, artifact: *CompileStep) *InstallArtifactStep {...@@ -40,6 +43,7 @@ pub fn create(owner: *std.Build, artifact: *CompileStep) *InstallArtifactStep {
40 }43 }
41 } else null,44 } else null,
42 .h_dir = if (artifact.kind == .lib and artifact.emit_h) .header else null,45 .h_dir = if (artifact.kind == .lib and artifact.emit_h) .header else null,
46 .dest_sub_path = null,
43 };47 };
44 self.step.dependOn(&artifact.step);48 self.step.dependOn(&artifact.step);
45 artifact.install_step = self;49 artifact.install_step = self;
...@@ -71,7 +75,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -71,7 +75,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
71 const self = @fieldParentPtr(InstallArtifactStep, "step", step);75 const self = @fieldParentPtr(InstallArtifactStep, "step", step);
72 const dest_builder = self.dest_builder;76 const dest_builder = self.dest_builder;
7377
74 const full_dest_path = dest_builder.getInstallPath(self.dest_dir, self.artifact.out_filename);78 const dest_sub_path = if (self.dest_sub_path) |sub_path| sub_path else self.artifact.out_filename;
79 const full_dest_path = dest_builder.getInstallPath(self.dest_dir, dest_sub_path);
80
75 try src_builder.updateFile(81 try src_builder.updateFile(
76 self.artifact.getOutputSource().getPath(src_builder),82 self.artifact.getOutputSource().getPath(src_builder),
77 full_dest_path,83 full_dest_path,
lib/std/Build/ObjCopyStep.zig+1-2
...@@ -95,8 +95,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -95,8 +95,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
95 const full_dest_path = try b.cache_root.join(b.allocator, &.{ "o", &digest, self.basename });95 const full_dest_path = try b.cache_root.join(b.allocator, &.{ "o", &digest, self.basename });
96 const cache_path = "o" ++ fs.path.sep_str ++ digest;96 const cache_path = "o" ++ fs.path.sep_str ++ digest;
97 b.cache_root.handle.makePath(cache_path) catch |err| {97 b.cache_root.handle.makePath(cache_path) catch |err| {
98 std.debug.print("unable to make path {s}: {s}\n", .{ cache_path, @errorName(err) });98 return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) });
99 return err;
100 };99 };
101100
102 var argv = std.ArrayList([]const u8).init(b.allocator);101 var argv = std.ArrayList([]const u8).init(b.allocator);
lib/std/Build/RunStep.zig+267-105
...@@ -10,6 +10,7 @@ const ArrayList = std.ArrayList;...@@ -10,6 +10,7 @@ const ArrayList = std.ArrayList;
10const EnvMap = process.EnvMap;10const EnvMap = process.EnvMap;
11const Allocator = mem.Allocator;11const Allocator = mem.Allocator;
12const ExecError = std.Build.ExecError;12const ExecError = std.Build.ExecError;
13const assert = std.debug.assert;
1314
14const RunStep = @This();15const RunStep = @This();
1516
...@@ -54,6 +55,8 @@ rename_step_with_output_arg: bool = true,...@@ -54,6 +55,8 @@ rename_step_with_output_arg: bool = true,
54/// Command-line arguments such as -fqemu and -fwasmtime may affect whether a55/// Command-line arguments such as -fqemu and -fwasmtime may affect whether a
55/// binary is detected as foreign, as well as system configuration such as56/// binary is detected as foreign, as well as system configuration such as
56/// Rosetta (macOS) and binfmt_misc (Linux).57/// Rosetta (macOS) and binfmt_misc (Linux).
58/// If this RunStep is considered to have side-effects, then this flag does
59/// nothing.
57skip_foreign_checks: bool = false,60skip_foreign_checks: bool = false,
5861
59/// If stderr or stdout exceeds this amount, the child process is killed and62/// If stderr or stdout exceeds this amount, the child process is killed and
...@@ -79,7 +82,7 @@ pub const StdIo = union(enum) {...@@ -79,7 +82,7 @@ pub const StdIo = union(enum) {
79 /// conditions.82 /// conditions.
80 /// Note that an explicit check for exit code 0 needs to be added to this83 /// Note that an explicit check for exit code 0 needs to be added to this
81 /// list if such a check is desireable.84 /// list if such a check is desireable.
82 check: []const Check,85 check: std.ArrayList(Check),
8386
84 pub const Check = union(enum) {87 pub const Check = union(enum) {
85 expect_stderr_exact: []const u8,88 expect_stderr_exact: []const u8,
...@@ -214,14 +217,20 @@ pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8...@@ -214,14 +217,20 @@ pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8
214 env_map.put(b.dupe(key), b.dupe(value)) catch @panic("unhandled error");217 env_map.put(b.dupe(key), b.dupe(value)) catch @panic("unhandled error");
215}218}
216219
220/// Adds a check for exact stderr match. Does not add any other checks.
217pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void {221pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void {
218 const new_check: StdIo.Check = .{ .expect_stderr_exact = self.step.owner.dupe(bytes) };222 const new_check: StdIo.Check = .{ .expect_stderr_exact = self.step.owner.dupe(bytes) };
219 self.addCheck(new_check);223 self.addCheck(new_check);
220}224}
221225
226/// Adds a check for exact stdout match as well as a check for exit code 0, if
227/// there is not already an expected termination check.
222pub fn expectStdOutEqual(self: *RunStep, bytes: []const u8) void {228pub fn expectStdOutEqual(self: *RunStep, bytes: []const u8) void {
223 const new_check: StdIo.Check = .{ .expect_stdout_exact = self.step.owner.dupe(bytes) };229 const new_check: StdIo.Check = .{ .expect_stdout_exact = self.step.owner.dupe(bytes) };
224 self.addCheck(new_check);230 self.addCheck(new_check);
231 if (!self.hasTermCheck()) {
232 self.expectExitCode(0);
233 }
225}234}
226235
227pub fn expectExitCode(self: *RunStep, code: u8) void {236pub fn expectExitCode(self: *RunStep, code: u8) void {
...@@ -229,19 +238,21 @@ pub fn expectExitCode(self: *RunStep, code: u8) void {...@@ -229,19 +238,21 @@ pub fn expectExitCode(self: *RunStep, code: u8) void {
229 self.addCheck(new_check);238 self.addCheck(new_check);
230}239}
231240
241pub fn hasTermCheck(self: RunStep) bool {
242 for (self.stdio.check.items) |check| switch (check) {
243 .expect_term => return true,
244 else => continue,
245 };
246 return false;
247}
248
232pub fn addCheck(self: *RunStep, new_check: StdIo.Check) void {249pub fn addCheck(self: *RunStep, new_check: StdIo.Check) void {
233 const arena = self.step.owner.allocator;
234 switch (self.stdio) {250 switch (self.stdio) {
235 .infer_from_args => {251 .infer_from_args => {
236 const list = arena.create([1]StdIo.Check) catch @panic("OOM");252 self.stdio = .{ .check = std.ArrayList(StdIo.Check).init(self.step.owner.allocator) };
237 list.* = .{new_check};253 self.stdio.check.append(new_check) catch @panic("OOM");
238 self.stdio = .{ .check = list };
239 },
240 .check => |checks| {
241 const new_list = arena.alloc(StdIo.Check, checks.len + 1) catch @panic("OOM");
242 std.mem.copy(StdIo.Check, new_list, checks);
243 new_list[checks.len] = new_check;
244 },254 },
255 .check => |*checks| checks.append(new_check) catch @panic("OOM"),
245 else => @panic("illegal call to addCheck: conflicting helper method calls. Suggest to directly set stdio field of RunStep instead"),256 else => @panic("illegal call to addCheck: conflicting helper method calls. Suggest to directly set stdio field of RunStep instead"),
246 }257 }
247}258}
...@@ -298,14 +309,15 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -298,14 +309,15 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
298 _ = prog_node;309 _ = prog_node;
299310
300 const b = step.owner;311 const b = step.owner;
312 const arena = b.allocator;
301 const self = @fieldParentPtr(RunStep, "step", step);313 const self = @fieldParentPtr(RunStep, "step", step);
302 const has_side_effects = self.hasSideEffects();314 const has_side_effects = self.hasSideEffects();
303315
304 var argv_list = ArrayList([]const u8).init(b.allocator);316 var argv_list = ArrayList([]const u8).init(arena);
305 var output_placeholders = ArrayList(struct {317 var output_placeholders = ArrayList(struct {
306 index: usize,318 index: usize,
307 output: Arg.Output,319 output: Arg.Output,
308 }).init(b.allocator);320 }).init(arena);
309321
310 var man = b.cache.obtain();322 var man = b.cache.obtain();
311 defer man.deinit();323 defer man.deinit();
...@@ -357,7 +369,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -357,7 +369,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
357 const digest = man.final();369 const digest = man.final();
358 for (output_placeholders.items) |placeholder| {370 for (output_placeholders.items) |placeholder| {
359 placeholder.output.generated_file.path = try b.cache_root.join(371 placeholder.output.generated_file.path = try b.cache_root.join(
360 b.allocator,372 arena,
361 &.{ "o", &digest, placeholder.output.basename },373 &.{ "o", &digest, placeholder.output.basename },
362 );374 );
363 }375 }
...@@ -367,30 +379,21 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -367,30 +379,21 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
367 const digest = man.final();379 const digest = man.final();
368380
369 for (output_placeholders.items) |placeholder| {381 for (output_placeholders.items) |placeholder| {
370 const output_path = try b.cache_root.join(382 const output_components = .{ "o", &digest, placeholder.output.basename };
371 b.allocator,383 const output_sub_path = try fs.path.join(arena, &output_components);
372 &.{ "o", &digest, placeholder.output.basename },384 const output_sub_dir_path = fs.path.dirname(output_sub_path).?;
373 );385 b.cache_root.handle.makePath(output_sub_dir_path) catch |err| {
374 const output_dir = fs.path.dirname(output_path).?;386 return step.fail("unable to make path '{}{s}': {s}", .{
375 fs.cwd().makePath(output_dir) catch |err| {387 b.cache_root, output_sub_dir_path, @errorName(err),
376 std.debug.print("unable to make path {s}: {s}\n", .{ output_dir, @errorName(err) });388 });
377 return err;
378 };389 };
379390 const output_path = try b.cache_root.join(arena, &output_components);
380 placeholder.output.generated_file.path = output_path;391 placeholder.output.generated_file.path = output_path;
381 argv_list.items[placeholder.index] = output_path;392 argv_list.items[placeholder.index] = output_path;
382 }393 }
383 }394 }
384395
385 try runCommand(396 try runCommand(self, argv_list.items, has_side_effects);
386 step,
387 self.cwd,
388 argv_list.items,
389 self.env_map,
390 self.stdio,
391 has_side_effects,
392 self.max_stdio_size,
393 );
394397
395 if (!has_side_effects) {398 if (!has_side_effects) {
396 try man.writeManifest();399 try man.writeManifest();
...@@ -442,92 +445,150 @@ fn termMatches(expected: ?std.ChildProcess.Term, actual: std.ChildProcess.Term)...@@ -442,92 +445,150 @@ fn termMatches(expected: ?std.ChildProcess.Term, actual: std.ChildProcess.Term)
442 };445 };
443}446}
444447
445fn runCommand(448fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool) !void {
446 step: *Step,449 const step = &self.step;
447 opt_cwd: ?[]const u8,
448 argv: []const []const u8,
449 env_map: ?*EnvMap,
450 stdio: StdIo,
451 has_side_effects: bool,
452 max_stdio_size: usize,
453) !void {
454 const b = step.owner;450 const b = step.owner;
455 const arena = b.allocator;451 const arena = b.allocator;
456 const cwd = if (opt_cwd) |cwd| b.pathFromRoot(cwd) else b.build_root.path;
457452
458 try step.handleChildProcUnsupported(opt_cwd, argv);453 try step.handleChildProcUnsupported(self.cwd, argv);
459 try Step.handleVerbose(step.owner, opt_cwd, argv);454 try Step.handleVerbose(step.owner, self.cwd, argv);
460
461 var child = std.ChildProcess.init(argv, arena);
462 child.cwd = cwd;
463 child.env_map = env_map orelse b.env_map;
464
465 child.stdin_behavior = switch (stdio) {
466 .infer_from_args => if (has_side_effects) .Inherit else .Ignore,
467 .inherit => .Inherit,
468 .check => .Close,
469 };
470 child.stdout_behavior = switch (stdio) {
471 .infer_from_args => if (has_side_effects) .Inherit else .Ignore,
472 .inherit => .Inherit,
473 .check => |checks| if (checksContainStdout(checks)) .Pipe else .Ignore,
474 };
475 child.stderr_behavior = switch (stdio) {
476 .infer_from_args => if (has_side_effects) .Inherit else .Pipe,
477 .inherit => .Inherit,
478 .check => .Pipe,
479 };
480
481 child.spawn() catch |err| return step.fail("unable to spawn {s}: {s}", .{
482 argv[0], @errorName(err),
483 });
484455
485 var stdout_bytes: ?[]const u8 = null;456 var stdout_bytes: ?[]const u8 = null;
486 var stderr_bytes: ?[]const u8 = null;457 var stderr_bytes: ?[]const u8 = null;
487458
488 if (child.stdout) |stdout| {459 const term = spawnChildAndCollect(self, argv, &stdout_bytes, &stderr_bytes, has_side_effects) catch |err| term: {
489 if (child.stderr) |stderr| {460 if (err == error.InvalidExe) interpret: {
490 var poller = std.io.poll(arena, enum { stdout, stderr }, .{461 // TODO: learn the target from the binary directly rather than from
491 .stdout = stdout,462 // relying on it being a CompileStep. This will make this logic
492 .stderr = stderr,463 // work even for the edge case that the binary was produced by a
493 });464 // third party.
494 defer poller.deinit();465 const exe = switch (self.argv.items[0]) {
466 .artifact => |exe| exe,
467 else => break :interpret,
468 };
469 if (exe.kind != .exe) break :interpret;
470
471 var interp_argv = std.ArrayList([]const u8).init(b.allocator);
472 defer interp_argv.deinit();
473
474 const need_cross_glibc = exe.target.isGnuLibC() and exe.is_linking_libc;
475 switch (b.host.getExternalExecutor(exe.target_info, .{
476 .qemu_fixes_dl = need_cross_glibc and b.glibc_runtimes_dir != null,
477 .link_libc = exe.is_linking_libc,
478 })) {
479 .native, .rosetta => {
480 if (self.stdio == .check and self.skip_foreign_checks)
481 return error.MakeSkipped;
482
483 break :interpret;
484 },
485 .wine => |bin_name| {
486 if (b.enable_wine) {
487 try interp_argv.append(bin_name);
488 } else {
489 return failForeign(self, "-fwine", argv[0], exe);
490 }
491 },
492 .qemu => |bin_name| {
493 if (b.enable_qemu) {
494 const glibc_dir_arg = if (need_cross_glibc)
495 b.glibc_runtimes_dir orelse return
496 else
497 null;
498
499 try interp_argv.append(bin_name);
500
501 if (glibc_dir_arg) |dir| {
502 // TODO look into making this a call to `linuxTriple`. This
503 // needs the directory to be called "i686" rather than
504 // "x86" which is why we do it manually here.
505 const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}";
506 const cpu_arch = exe.target.getCpuArch();
507 const os_tag = exe.target.getOsTag();
508 const abi = exe.target.getAbi();
509 const cpu_arch_name: []const u8 = if (cpu_arch == .x86)
510 "i686"
511 else
512 @tagName(cpu_arch);
513 const full_dir = try std.fmt.allocPrint(b.allocator, fmt_str, .{
514 dir, cpu_arch_name, @tagName(os_tag), @tagName(abi),
515 });
516
517 try interp_argv.append("-L");
518 try interp_argv.append(full_dir);
519 }
520 } else {
521 return failForeign(self, "-fqemu", argv[0], exe);
522 }
523 },
524 .darling => |bin_name| {
525 if (b.enable_darling) {
526 try interp_argv.append(bin_name);
527 } else {
528 return failForeign(self, "-fdarling", argv[0], exe);
529 }
530 },
531 .wasmtime => |bin_name| {
532 if (b.enable_wasmtime) {
533 try interp_argv.append(bin_name);
534 try interp_argv.append("--dir=.");
535 } else {
536 return failForeign(self, "-fwasmtime", argv[0], exe);
537 }
538 },
539 .bad_dl => |foreign_dl| {
540 if (self.stdio == .check and self.skip_foreign_checks)
541 return error.MakeSkipped;
542
543 const host_dl = b.host.dynamic_linker.get() orelse "(none)";
495544
496 while (try poller.poll()) {545 return step.fail(
497 if (poller.fifo(.stdout).count > max_stdio_size)546 \\the host system is unable to execute binaries from the target
498 return error.StdoutStreamTooLong;547 \\ because the host dynamic linker is '{s}',
499 if (poller.fifo(.stderr).count > max_stdio_size)548 \\ while the target dynamic linker is '{s}'.
500 return error.StderrStreamTooLong;549 \\ consider setting the dynamic linker or enabling skip_foreign_checks in the Run step
550 , .{ host_dl, foreign_dl });
551 },
552 .bad_os_or_cpu => {
553 if (self.stdio == .check and self.skip_foreign_checks)
554 return error.MakeSkipped;
555
556 const host_name = try b.host.target.zigTriple(b.allocator);
557 const foreign_name = try exe.target.zigTriple(b.allocator);
558
559 return step.fail("the host system ({s}) is unable to execute binaries from the target ({s})", .{
560 host_name, foreign_name,
561 });
562 },
501 }563 }
502564
503 stdout_bytes = try poller.fifo(.stdout).toOwnedSlice();565 if (exe.target.isWindows()) {
504 stderr_bytes = try poller.fifo(.stderr).toOwnedSlice();566 // On Windows we don't have rpaths so we have to add .dll search paths to PATH
505 } else {567 RunStep.addPathForDynLibsInternal(&self.step, b, exe);
506 stdout_bytes = try stdout.reader().readAllAlloc(arena, max_stdio_size);568 }
507 }
508 } else if (child.stderr) |stderr| {
509 stderr_bytes = try stderr.reader().readAllAlloc(arena, max_stdio_size);
510 }
511569
512 if (stderr_bytes) |stderr| if (stderr.len > 0) {570 try interp_argv.append(argv[0]);
513 const stderr_is_diagnostic = switch (stdio) {571
514 .check => |checks| !checksContainStderr(checks),572 try Step.handleVerbose(step.owner, self.cwd, interp_argv.items);
515 else => true,573
516 };574 assert(stdout_bytes == null);
517 if (stderr_is_diagnostic) {575 assert(stderr_bytes == null);
518 try step.result_error_msgs.append(arena, stderr);576 break :term spawnChildAndCollect(self, interp_argv.items, &stdout_bytes, &stderr_bytes, has_side_effects) catch |inner_err| {
577 return step.fail("unable to spawn {s}: {s}", .{
578 interp_argv.items[0], @errorName(inner_err),
579 });
580 };
519 }581 }
520 };
521582
522 const term = child.wait() catch |err| {583 return step.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err) });
523 return step.fail("unable to wait for {s}: {s}", .{ argv[0], @errorName(err) });
524 };584 };
525585
526 switch (stdio) {586 switch (self.stdio) {
527 .check => |checks| for (checks) |check| switch (check) {587 .check => |checks| for (checks.items) |check| switch (check) {
528 .expect_stderr_exact => |expected_bytes| {588 .expect_stderr_exact => |expected_bytes| {
529 if (!mem.eql(u8, expected_bytes, stderr_bytes.?)) {589 if (!mem.eql(u8, expected_bytes, stderr_bytes.?)) {
530 return step.fail(590 return step.fail(
591 \\
531 \\========= expected this stderr: =========592 \\========= expected this stderr: =========
532 \\{s}593 \\{s}
533 \\========= but found: ====================594 \\========= but found: ====================
...@@ -537,13 +598,14 @@ fn runCommand(...@@ -537,13 +598,14 @@ fn runCommand(
537 , .{598 , .{
538 expected_bytes,599 expected_bytes,
539 stderr_bytes.?,600 stderr_bytes.?,
540 try Step.allocPrintCmd(arena, opt_cwd, argv),601 try Step.allocPrintCmd(arena, self.cwd, argv),
541 });602 });
542 }603 }
543 },604 },
544 .expect_stderr_match => |match| {605 .expect_stderr_match => |match| {
545 if (mem.indexOf(u8, stderr_bytes.?, match) == null) {606 if (mem.indexOf(u8, stderr_bytes.?, match) == null) {
546 return step.fail(607 return step.fail(
608 \\
547 \\========= expected to find in stderr: =========609 \\========= expected to find in stderr: =========
548 \\{s}610 \\{s}
549 \\========= but stderr does not contain it: =====611 \\========= but stderr does not contain it: =====
...@@ -553,13 +615,14 @@ fn runCommand(...@@ -553,13 +615,14 @@ fn runCommand(
553 , .{615 , .{
554 match,616 match,
555 stderr_bytes.?,617 stderr_bytes.?,
556 try Step.allocPrintCmd(arena, opt_cwd, argv),618 try Step.allocPrintCmd(arena, self.cwd, argv),
557 });619 });
558 }620 }
559 },621 },
560 .expect_stdout_exact => |expected_bytes| {622 .expect_stdout_exact => |expected_bytes| {
561 if (!mem.eql(u8, expected_bytes, stdout_bytes.?)) {623 if (!mem.eql(u8, expected_bytes, stdout_bytes.?)) {
562 return step.fail(624 return step.fail(
625 \\
563 \\========= expected this stdout: =========626 \\========= expected this stdout: =========
564 \\{s}627 \\{s}
565 \\========= but found: ====================628 \\========= but found: ====================
...@@ -569,13 +632,14 @@ fn runCommand(...@@ -569,13 +632,14 @@ fn runCommand(
569 , .{632 , .{
570 expected_bytes,633 expected_bytes,
571 stdout_bytes.?,634 stdout_bytes.?,
572 try Step.allocPrintCmd(arena, opt_cwd, argv),635 try Step.allocPrintCmd(arena, self.cwd, argv),
573 });636 });
574 }637 }
575 },638 },
576 .expect_stdout_match => |match| {639 .expect_stdout_match => |match| {
577 if (mem.indexOf(u8, stdout_bytes.?, match) == null) {640 if (mem.indexOf(u8, stdout_bytes.?, match) == null) {
578 return step.fail(641 return step.fail(
642 \\
579 \\========= expected to find in stdout: =========643 \\========= expected to find in stdout: =========
580 \\{s}644 \\{s}
581 \\========= but stdout does not contain it: =====645 \\========= but stdout does not contain it: =====
...@@ -585,7 +649,7 @@ fn runCommand(...@@ -585,7 +649,7 @@ fn runCommand(
585 , .{649 , .{
586 match,650 match,
587 stdout_bytes.?,651 stdout_bytes.?,
588 try Step.allocPrintCmd(arena, opt_cwd, argv),652 try Step.allocPrintCmd(arena, self.cwd, argv),
589 });653 });
590 }654 }
591 },655 },
...@@ -594,17 +658,89 @@ fn runCommand(...@@ -594,17 +658,89 @@ fn runCommand(
594 return step.fail("the following command {} (expected {}):\n{s}", .{658 return step.fail("the following command {} (expected {}):\n{s}", .{
595 fmtTerm(term),659 fmtTerm(term),
596 fmtTerm(expected_term),660 fmtTerm(expected_term),
597 try Step.allocPrintCmd(arena, opt_cwd, argv),661 try Step.allocPrintCmd(arena, self.cwd, argv),
598 });662 });
599 }663 }
600 },664 },
601 },665 },
602 else => {666 else => {
603 try step.handleChildProcessTerm(term, opt_cwd, argv);667 try step.handleChildProcessTerm(term, self.cwd, argv);
604 },668 },
605 }669 }
606}670}
607671
672fn spawnChildAndCollect(
673 self: *RunStep,
674 argv: []const []const u8,
675 stdout_bytes: *?[]const u8,
676 stderr_bytes: *?[]const u8,
677 has_side_effects: bool,
678) !std.ChildProcess.Term {
679 const b = self.step.owner;
680 const arena = b.allocator;
681 const cwd = if (self.cwd) |cwd| b.pathFromRoot(cwd) else b.build_root.path;
682
683 var child = std.ChildProcess.init(argv, arena);
684 child.cwd = cwd;
685 child.env_map = self.env_map orelse b.env_map;
686
687 child.stdin_behavior = switch (self.stdio) {
688 .infer_from_args => if (has_side_effects) .Inherit else .Ignore,
689 .inherit => .Inherit,
690 .check => .Close,
691 };
692 child.stdout_behavior = switch (self.stdio) {
693 .infer_from_args => if (has_side_effects) .Inherit else .Ignore,
694 .inherit => .Inherit,
695 .check => |checks| if (checksContainStdout(checks.items)) .Pipe else .Ignore,
696 };
697 child.stderr_behavior = switch (self.stdio) {
698 .infer_from_args => if (has_side_effects) .Inherit else .Pipe,
699 .inherit => .Inherit,
700 .check => .Pipe,
701 };
702
703 child.spawn() catch |err| return self.step.fail("unable to spawn {s}: {s}", .{
704 argv[0], @errorName(err),
705 });
706
707 if (child.stdout) |stdout| {
708 if (child.stderr) |stderr| {
709 var poller = std.io.poll(arena, enum { stdout, stderr }, .{
710 .stdout = stdout,
711 .stderr = stderr,
712 });
713 defer poller.deinit();
714
715 while (try poller.poll()) {
716 if (poller.fifo(.stdout).count > self.max_stdio_size)
717 return error.StdoutStreamTooLong;
718 if (poller.fifo(.stderr).count > self.max_stdio_size)
719 return error.StderrStreamTooLong;
720 }
721
722 stdout_bytes.* = try poller.fifo(.stdout).toOwnedSlice();
723 stderr_bytes.* = try poller.fifo(.stderr).toOwnedSlice();
724 } else {
725 stdout_bytes.* = try stdout.reader().readAllAlloc(arena, self.max_stdio_size);
726 }
727 } else if (child.stderr) |stderr| {
728 stderr_bytes.* = try stderr.reader().readAllAlloc(arena, self.max_stdio_size);
729 }
730
731 if (stderr_bytes.*) |stderr| if (stderr.len > 0) {
732 const stderr_is_diagnostic = switch (self.stdio) {
733 .check => |checks| !checksContainStderr(checks.items),
734 else => true,
735 };
736 if (stderr_is_diagnostic) {
737 try self.step.result_error_msgs.append(arena, stderr);
738 }
739 };
740
741 return child.wait();
742}
743
608fn addPathForDynLibs(self: *RunStep, artifact: *CompileStep) void {744fn addPathForDynLibs(self: *RunStep, artifact: *CompileStep) void {
609 addPathForDynLibsInternal(&self.step, self.step.owner, artifact);745 addPathForDynLibsInternal(&self.step, self.step.owner, artifact);
610}746}
...@@ -624,3 +760,29 @@ pub fn addPathForDynLibsInternal(step: *Step, builder: *std.Build, artifact: *Co...@@ -624,3 +760,29 @@ pub fn addPathForDynLibsInternal(step: *Step, builder: *std.Build, artifact: *Co
624 }760 }
625 }761 }
626}762}
763
764fn failForeign(
765 self: *RunStep,
766 suggested_flag: []const u8,
767 argv0: []const u8,
768 exe: *CompileStep,
769) error{ MakeFailed, MakeSkipped, OutOfMemory } {
770 switch (self.stdio) {
771 .check => {
772 if (self.skip_foreign_checks)
773 return error.MakeSkipped;
774
775 const b = self.step.owner;
776 const host_name = try b.host.target.zigTriple(b.allocator);
777 const foreign_name = try exe.target.zigTriple(b.allocator);
778
779 return self.step.fail(
780 \\unable to spawn foreign binary '{s}' ({s}) on host system ({s})
781 \\ consider using {s} or enabling skip_foreign_checks in the Run step
782 , .{ argv0, foreign_name, host_name, suggested_flag });
783 },
784 else => {
785 return self.step.fail("unable to spawn foreign binary '{s}'", .{argv0});
786 },
787 }
788}
lib/std/Build/Step.zig+16-7
...@@ -26,6 +26,9 @@ pub const State = enum {...@@ -26,6 +26,9 @@ pub const State = enum {
26 dependency_failure,26 dependency_failure,
27 success,27 success,
28 failure,28 failure,
29 /// This state indicates that the step did not complete, however, it also did not fail,
30 /// and it is safe to continue executing its dependencies.
31 skipped,
29};32};
3033
31pub const Id = enum {34pub const Id = enum {
...@@ -106,13 +109,15 @@ pub fn init(options: Options) Step {...@@ -106,13 +109,15 @@ pub fn init(options: Options) Step {
106/// If the Step's `make` function reports `error.MakeFailed`, it indicates they109/// If the Step's `make` function reports `error.MakeFailed`, it indicates they
107/// have already reported the error. Otherwise, we add a simple error report110/// have already reported the error. Otherwise, we add a simple error report
108/// here.111/// here.
109pub fn make(s: *Step, prog_node: *std.Progress.Node) error{MakeFailed}!void {112pub fn make(s: *Step, prog_node: *std.Progress.Node) error{ MakeFailed, MakeSkipped }!void {
110 return s.makeFn(s, prog_node) catch |err| {113 return s.makeFn(s, prog_node) catch |err| switch (err) {
111 if (err != error.MakeFailed) {114 error.MakeFailed => return error.MakeFailed,
115 error.MakeSkipped => return error.MakeSkipped,
116 else => {
112 const gpa = s.dependencies.allocator;117 const gpa = s.dependencies.allocator;
113 s.result_error_msgs.append(gpa, @errorName(err)) catch @panic("OOM");118 s.result_error_msgs.append(gpa, @errorName(err)) catch @panic("OOM");
114 }119 return error.MakeFailed;
115 return error.MakeFailed;120 },
116 };121 };
117}122}
118123
...@@ -192,10 +197,14 @@ pub fn evalChildProcess(s: *Step, argv: []const []const u8) !void {...@@ -192,10 +197,14 @@ pub fn evalChildProcess(s: *Step, argv: []const []const u8) !void {
192}197}
193198
194pub fn fail(step: *Step, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, MakeFailed } {199pub fn fail(step: *Step, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, MakeFailed } {
200 try step.addError(fmt, args);
201 return error.MakeFailed;
202}
203
204pub fn addError(step: *Step, comptime fmt: []const u8, args: anytype) error{OutOfMemory}!void {
195 const arena = step.owner.allocator;205 const arena = step.owner.allocator;
196 const msg = try std.fmt.allocPrint(arena, fmt, args);206 const msg = try std.fmt.allocPrint(arena, fmt, args);
197 try step.result_error_msgs.append(arena, msg);207 try step.result_error_msgs.append(arena, msg);
198 return error.MakeFailed;
199}208}
200209
201/// Assumes that argv contains `--listen=-` and that the process being spawned210/// 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...@@ -398,5 +407,5 @@ fn failWithCacheError(s: *Step, man: *const std.Build.Cache.Manifest, err: anyer
398 const i = man.failed_file_index orelse return err;407 const i = man.failed_file_index orelse return err;
399 const pp = man.files.items[i].prefixed_path orelse return err;408 const pp = man.files.items[i].prefixed_path orelse return err;
400 const prefix = man.cache.prefixes()[pp.prefix].path orelse "";409 const prefix = man.cache.prefixes()[pp.prefix].path orelse "";
401 return s.fail("{s}: {s}/{s}\n", .{ @errorName(err), prefix, pp.sub_path });410 return s.fail("{s}: {s}/{s}", .{ @errorName(err), prefix, pp.sub_path });
402}411}
lib/std/Build/WriteFileStep.zig+86-28
...@@ -37,7 +37,7 @@ pub fn init(owner: *std.Build) WriteFileStep {...@@ -37,7 +37,7 @@ pub fn init(owner: *std.Build) WriteFileStep {
37 return .{37 return .{
38 .step = Step.init(.{38 .step = Step.init(.{
39 .id = .write_file,39 .id = .write_file,
40 .name = "writefile",40 .name = "WriteFile",
41 .owner = owner,41 .owner = owner,
42 .makeFn = make,42 .makeFn = make,
43 }),43 }),
...@@ -56,6 +56,8 @@ pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void {...@@ -56,6 +56,8 @@ pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void {
56 .contents = .{ .bytes = b.dupe(bytes) },56 .contents = .{ .bytes = b.dupe(bytes) },
57 };57 };
58 wf.files.append(gpa, file) catch @panic("OOM");58 wf.files.append(gpa, file) catch @panic("OOM");
59
60 wf.maybeUpdateName();
59}61}
6062
61/// Place the file into the generated directory within the local cache,63/// 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: [...@@ -75,6 +77,8 @@ pub fn addCopyFile(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: [
75 .contents = .{ .copy = source },77 .contents = .{ .copy = source },
76 };78 };
77 wf.files.append(gpa, file) catch @panic("OOM");79 wf.files.append(gpa, file) catch @panic("OOM");
80
81 wf.maybeUpdateName();
78}82}
7983
80/// A path relative to the package root.84/// A path relative to the package root.
...@@ -101,6 +105,15 @@ pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSo...@@ -101,6 +105,15 @@ pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSo
101 return null;105 return null;
102}106}
103107
108fn maybeUpdateName(wf: *WriteFileStep) void {
109 if (wf.files.items.len == 1) {
110 // First time adding a file; update name.
111 if (std.mem.eql(u8, wf.step.name, "WriteFile")) {
112 wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wf.files.items[0].sub_path});
113 }
114 }
115}
116
104fn make(step: *Step, prog_node: *std.Progress.Node) !void {117fn make(step: *Step, prog_node: *std.Progress.Node) !void {
105 _ = prog_node;118 _ = prog_node;
106 const b = step.owner;119 const b = step.owner;
...@@ -110,14 +123,39 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -110,14 +123,39 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
110 // WriteFileStep - arguably it should be a different step. But anyway here123 // WriteFileStep - arguably it should be a different step. But anyway here
111 // it is, it happens unconditionally and does not interact with the other124 // it is, it happens unconditionally and does not interact with the other
112 // files here.125 // files here.
126 var any_miss = false;
113 for (wf.output_source_files.items) |output_source_file| {127 for (wf.output_source_files.items) |output_source_file| {
114 const basename = fs.path.basename(output_source_file.sub_path);
115 if (fs.path.dirname(output_source_file.sub_path)) |dirname| {128 if (fs.path.dirname(output_source_file.sub_path)) |dirname| {
116 var dir = try b.build_root.handle.makeOpenPath(dirname, .{});129 b.build_root.handle.makePath(dirname) catch |err| {
117 defer dir.close();130 return step.fail("unable to make path '{}{s}': {s}", .{
118 try writeFile(wf, dir, output_source_file.contents, basename);131 b.build_root, dirname, @errorName(err),
119 } else {132 });
120 try writeFile(wf, b.build_root.handle, output_source_file.contents, basename);133 };
134 }
135 switch (output_source_file.contents) {
136 .bytes => |bytes| {
137 b.build_root.handle.writeFile(output_source_file.sub_path, bytes) catch |err| {
138 return step.fail("unable to write file '{}{s}': {s}", .{
139 b.build_root, output_source_file.sub_path, @errorName(err),
140 });
141 };
142 any_miss = true;
143 },
144 .copy => |file_source| {
145 const source_path = file_source.getPath(b);
146 const prev_status = fs.Dir.updateFile(
147 fs.cwd(),
148 source_path,
149 b.build_root.handle,
150 output_source_file.sub_path,
151 .{},
152 ) catch |err| {
153 return step.fail("unable to update file from '{s}' to '{}{s}': {s}", .{
154 source_path, b.build_root, output_source_file.sub_path, @errorName(err),
155 });
156 };
157 any_miss = any_miss or prev_status == .stale;
158 },
121 }159 }
122 }160 }
123161
...@@ -164,19 +202,52 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -164,19 +202,52 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
164 const cache_path = "o" ++ fs.path.sep_str ++ digest;202 const cache_path = "o" ++ fs.path.sep_str ++ digest;
165203
166 var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| {204 var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| {
167 std.debug.print("unable to make path {s}: {s}\n", .{ cache_path, @errorName(err) });205 return step.fail("unable to make path '{}{s}': {s}", .{
168 return err;206 b.cache_root, cache_path, @errorName(err),
207 });
169 };208 };
170 defer cache_dir.close();209 defer cache_dir.close();
171210
172 for (wf.files.items) |file| {211 for (wf.files.items) |file| {
173 const basename = fs.path.basename(file.sub_path);
174 if (fs.path.dirname(file.sub_path)) |dirname| {212 if (fs.path.dirname(file.sub_path)) |dirname| {
175 var dir = try b.cache_root.handle.makeOpenPath(dirname, .{});213 cache_dir.makePath(dirname) catch |err| {
176 defer dir.close();214 return step.fail("unable to make path '{}{s}{c}{s}': {s}", .{
177 try writeFile(wf, dir, file.contents, basename);215 b.cache_root, cache_path, fs.path.sep, dirname, @errorName(err),
178 } else {216 });
179 try writeFile(wf, cache_dir, file.contents, basename);217 };
218 }
219 switch (file.contents) {
220 .bytes => |bytes| {
221 cache_dir.writeFile(file.sub_path, bytes) catch |err| {
222 return step.fail("unable to write file '{}{s}{c}{s}': {s}", .{
223 b.cache_root, cache_path, fs.path.sep, file.sub_path, @errorName(err),
224 });
225 };
226 },
227 .copy => |file_source| {
228 const source_path = file_source.getPath(b);
229 const prev_status = fs.Dir.updateFile(
230 fs.cwd(),
231 source_path,
232 cache_dir,
233 file.sub_path,
234 .{},
235 ) catch |err| {
236 return step.fail("unable to update file from '{s}' to '{}{s}{c}{s}': {s}", .{
237 source_path,
238 b.cache_root,
239 cache_path,
240 fs.path.sep,
241 file.sub_path,
242 @errorName(err),
243 });
244 };
245 // At this point we already will mark the step as a cache miss.
246 // But this is kind of a partial cache hit since individual
247 // file copies may be avoided. Oh well, this information is
248 // discarded.
249 _ = prev_status;
250 },
180 }251 }
181252
182 file.generated_file.path = try b.cache_root.join(253 file.generated_file.path = try b.cache_root.join(
...@@ -188,19 +259,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -188,19 +259,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
188 try man.writeManifest();259 try man.writeManifest();
189}260}
190261
191fn writeFile(wf: *WriteFileStep, dir: fs.Dir, contents: Contents, basename: []const u8) !void {
192 const b = wf.step.owner;
193 // TODO after landing concurrency PR, improve error reporting here
194 switch (contents) {
195 .bytes => |bytes| return dir.writeFile(basename, bytes),
196 .copy => |file_source| {
197 const source_path = file_source.getPath(b);
198 const prev_status = try fs.Dir.updateFile(fs.cwd(), source_path, dir, basename, .{});
199 _ = prev_status; // TODO logging (affected by open PR regarding concurrency)
200 },
201 }
202}
203
204const std = @import("../std.zig");262const std = @import("../std.zig");
205const Step = std.Build.Step;263const Step = std.Build.Step;
206const fs = std.fs;264const fs = std.fs;
lib/std/child_process.zig-1
...@@ -185,7 +185,6 @@ pub const ChildProcess = struct {...@@ -185,7 +185,6 @@ pub const ChildProcess = struct {
185 }185 }
186186
187 /// Blocks until child process terminates and then cleans up all resources.187 /// Blocks until child process terminates and then cleans up all resources.
188 /// TODO: set the pid to undefined in this function.
189 pub fn wait(self: *ChildProcess) !Term {188 pub fn wait(self: *ChildProcess) !Term {
190 const term = if (builtin.os.tag == .windows)189 const term = if (builtin.os.tag == .windows)
191 try self.waitWindows()190 try self.waitWindows()
test/link/macho/bugs/13457/build.zig+3-1
...@@ -13,6 +13,8 @@ pub fn build(b: *std.Build) void {...@@ -13,6 +13,8 @@ pub fn build(b: *std.Build) void {
13 .target = target,13 .target = target,
14 });14 });
1515
16 const run = exe.runEmulatable();16 const run = b.addRunArtifact(exe);
17 run.skip_foreign_checks = true;
18 run.expectStdOutEqual("");
17 test_step.dependOn(&run.step);19 test_step.dependOn(&run.step);
18}20}
test/link/macho/empty/build.zig+2-1
...@@ -16,7 +16,8 @@ pub fn build(b: *std.Build) void {...@@ -16,7 +16,8 @@ pub fn build(b: *std.Build) void {
16 exe.addCSourceFile("empty.c", &[0][]const u8{});16 exe.addCSourceFile("empty.c", &[0][]const u8{});
17 exe.linkLibC();17 exe.linkLibC();
1818
19 const run_cmd = std.Build.EmulatableRunStep.create(b, "run", exe);19 const run_cmd = b.addRunArtifact(exe);
20 run_cmd.skip_foreign_checks = true;
20 run_cmd.expectStdOutEqual("Hello!\n");21 run_cmd.expectStdOutEqual("Hello!\n");
21 test_step.dependOn(&run_cmd.step);22 test_step.dependOn(&run_cmd.step);
22}23}
test/link/macho/needed_library/build.zig+1
...@@ -36,5 +36,6 @@ pub fn build(b: *std.Build) void {...@@ -36,5 +36,6 @@ pub fn build(b: *std.Build) void {
36 check.checkNext("name @rpath/liba.dylib");36 check.checkNext("name @rpath/liba.dylib");
3737
38 const run_cmd = check.runAndCompare();38 const run_cmd = check.runAndCompare();
39 run_cmd.expectStdOutEqual("");
39 test_step.dependOn(&run_cmd.step);40 test_step.dependOn(&run_cmd.step);
40}41}
test/link/macho/objc/build.zig+3-1
...@@ -17,6 +17,8 @@ pub fn build(b: *std.Build) void {...@@ -17,6 +17,8 @@ pub fn build(b: *std.Build) void {
17 // populate paths to the sysroot here.17 // populate paths to the sysroot here.
18 exe.linkFramework("Foundation");18 exe.linkFramework("Foundation");
1919
20 const run_cmd = std.Build.EmulatableRunStep.create(b, "run", exe);20 const run_cmd = b.addRunArtifact(exe);
21 run_cmd.skip_foreign_checks = true;
22 run_cmd.expectStdOutEqual("");
21 test_step.dependOn(&run_cmd.step);23 test_step.dependOn(&run_cmd.step);
22}24}
test/link/macho/search_strategy/build.zig+2-1
...@@ -27,7 +27,8 @@ pub fn build(b: *std.Build) void {...@@ -27,7 +27,8 @@ pub fn build(b: *std.Build) void {
27 const exe = createScenario(b, optimize, target);27 const exe = createScenario(b, optimize, target);
28 exe.search_strategy = .paths_first;28 exe.search_strategy = .paths_first;
2929
30 const run = std.Build.EmulatableRunStep.create(b, "run", exe);30 const run = b.addRunArtifact(exe);
31 run.skip_foreign_checks = true;
31 run.cwd = b.pathFromRoot(".");32 run.cwd = b.pathFromRoot(".");
32 run.expectStdOutEqual("Hello world");33 run.expectStdOutEqual("Hello world");
33 test_step.dependOn(&run.step);34 test_step.dependOn(&run.step);
test/link/macho/stack_size/build.zig+1
...@@ -21,5 +21,6 @@ pub fn build(b: *std.Build) void {...@@ -21,5 +21,6 @@ pub fn build(b: *std.Build) void {
21 check_exe.checkNext("stacksize 100000000");21 check_exe.checkNext("stacksize 100000000");
2222
23 const run = check_exe.runAndCompare();23 const run = check_exe.runAndCompare();
24 run.expectStdOutEqual("");
24 test_step.dependOn(&run.step);25 test_step.dependOn(&run.step);
25}26}
test/link/macho/uuid/build.zig+16-60
...@@ -1,5 +1,4 @@...@@ -1,5 +1,4 @@
1const std = @import("std");1const std = @import("std");
2const Builder = std.Build.Builder;
3const CompileStep = std.Build.CompileStep;2const CompileStep = std.Build.CompileStep;
4const FileSource = std.Build.FileSource;3const FileSource = std.Build.FileSource;
5const Step = std.Build.Step;4const Step = std.Build.Step;
...@@ -38,13 +37,15 @@ fn testUuid(...@@ -38,13 +37,15 @@ fn testUuid(
38 // stay the same across builds.37 // stay the same across builds.
39 {38 {
40 const dylib = simpleDylib(b, optimize, target);39 const dylib = simpleDylib(b, optimize, target);
41 const install_step = installWithRename(dylib, "test1.dylib");40 const install_step = b.addInstallArtifact(dylib);
41 install_step.dest_sub_path = "test1.dylib";
42 install_step.step.dependOn(&dylib.step);42 install_step.step.dependOn(&dylib.step);
43 }43 }
44 {44 {
45 const dylib = simpleDylib(b, optimize, target);45 const dylib = simpleDylib(b, optimize, target);
46 dylib.strip = true;46 dylib.strip = true;
47 const install_step = installWithRename(dylib, "test2.dylib");47 const install_step = b.addInstallArtifact(dylib);
48 install_step.dest_sub_path = "test2.dylib";
48 install_step.step.dependOn(&dylib.step);49 install_step.step.dependOn(&dylib.step);
49 }50 }
5051
...@@ -68,70 +69,23 @@ fn simpleDylib(...@@ -68,70 +69,23 @@ fn simpleDylib(
68 return dylib;69 return dylib;
69}70}
7071
71fn installWithRename(cs: *CompileStep, name: []const u8) *InstallWithRename {
72 const step = InstallWithRename.create(cs.builder, cs.getOutputSource(), name);
73 cs.builder.getInstallStep().dependOn(&step.step);
74 return step;
75}
76
77const InstallWithRename = struct {
78 pub const base_id = .custom;
79
80 step: Step,
81 builder: *Builder,
82 source: FileSource,
83 name: []const u8,
84
85 pub fn create(
86 builder: *Builder,
87 source: FileSource,
88 name: []const u8,
89 ) *InstallWithRename {
90 const self = builder.allocator.create(InstallWithRename) catch @panic("OOM");
91 self.* = InstallWithRename{
92 .builder = builder,
93 .step = Step.init(builder.allocator, .{
94 .id = .custom,
95 .name = builder.fmt("install and rename: {s} -> {s}", .{
96 source.getDisplayName(), name,
97 }),
98 .makeFn = make,
99 }),
100 .source = source,
101 .name = builder.dupe(name),
102 };
103 return self;
104 }
105
106 fn make(step: *Step) anyerror!void {
107 const self = @fieldParentPtr(InstallWithRename, "step", step);
108 const source_path = self.source.getPath(self.builder);
109 const target_path = self.builder.getInstallPath(.lib, self.name);
110 self.builder.updateFile(source_path, target_path) catch |err| {
111 std.log.err("Unable to rename: {s} -> {s}", .{ source_path, target_path });
112 return err;
113 };
114 }
115};
116
117const CompareUuid = struct {72const CompareUuid = struct {
118 pub const base_id = .custom;73 pub const base_id = .custom;
11974
120 step: Step,75 step: Step,
121 builder: *Builder,
122 lhs: []const u8,76 lhs: []const u8,
123 rhs: []const u8,77 rhs: []const u8,
12478
125 pub fn create(builder: *Builder, lhs: []const u8, rhs: []const u8) *CompareUuid {79 pub fn create(owner: *std.Build, lhs: []const u8, rhs: []const u8) *CompareUuid {
126 const self = builder.allocator.create(CompareUuid) catch @panic("OOM");80 const self = owner.allocator.create(CompareUuid) catch @panic("OOM");
127 self.* = CompareUuid{81 self.* = CompareUuid{
128 .builder = builder,82 .step = Step.init(.{
129 .step = Step.init(builder.allocator, .{83 .id = base_id,
130 .id = .custom,84 .name = owner.fmt("compare uuid: {s} and {s}", .{
131 .name = builder.fmt("compare uuid: {s} and {s}", .{
132 lhs,85 lhs,
133 rhs,86 rhs,
134 }),87 }),
88 .owner = owner,
135 .makeFn = make,89 .makeFn = make,
136 }),90 }),
137 .lhs = lhs,91 .lhs = lhs,
...@@ -140,16 +94,18 @@ const CompareUuid = struct {...@@ -140,16 +94,18 @@ const CompareUuid = struct {
140 return self;94 return self;
141 }95 }
14296
143 fn make(step: *Step) anyerror!void {97 fn make(step: *Step, prog_node: *std.Progress.Node) anyerror!void {
98 _ = prog_node;
99 const b = step.owner;
144 const self = @fieldParentPtr(CompareUuid, "step", step);100 const self = @fieldParentPtr(CompareUuid, "step", step);
145 const gpa = self.builder.allocator;101 const gpa = b.allocator;
146102
147 var lhs_uuid: [16]u8 = undefined;103 var lhs_uuid: [16]u8 = undefined;
148 const lhs_path = self.builder.getInstallPath(.lib, self.lhs);104 const lhs_path = b.getInstallPath(.lib, self.lhs);
149 try parseUuid(gpa, lhs_path, &lhs_uuid);105 try parseUuid(gpa, lhs_path, &lhs_uuid);
150106
151 var rhs_uuid: [16]u8 = undefined;107 var rhs_uuid: [16]u8 = undefined;
152 const rhs_path = self.builder.getInstallPath(.lib, self.rhs);108 const rhs_path = b.getInstallPath(.lib, self.rhs);
153 try parseUuid(gpa, rhs_path, &rhs_uuid);109 try parseUuid(gpa, rhs_path, &rhs_uuid);
154110
155 try std.testing.expectEqualStrings(&lhs_uuid, &rhs_uuid);111 try std.testing.expectEqualStrings(&lhs_uuid, &rhs_uuid);
test/link/wasm/extern/build.zig+2-1
...@@ -11,7 +11,8 @@ pub fn build(b: *std.Build) void {...@@ -11,7 +11,8 @@ pub fn build(b: *std.Build) void {
11 exe.use_llvm = false;11 exe.use_llvm = false;
12 exe.use_lld = false;12 exe.use_lld = false;
1313
14 const run = exe.runEmulatable();14 const run = b.addRunArtifact(exe);
15 run.skip_foreign_checks = true;
15 run.expectStdOutEqual("Result: 30");16 run.expectStdOutEqual("Result: 30");
1617
17 const test_step = b.step("test", "Run linker test");18 const test_step = b.step("test", "Run linker test");