authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-03-03 13:23:22-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-03-15 10:48:13-07:00
log8b054e190a977ccd27302debd5d0f95c28597005
treeaaf41c622d3f22a38de5524e06446533cdc75740
parent677a0e294116b0fcc1d69eea99c2fa62eb9873fe

std.Build.RunStep: work around a miscompilation

See #14783 Also, set the cwd directory handle when spawning the child process if available.

1 files changed, 67 insertions(+), 37 deletions(-)

lib/std/Build/RunStep.zig+67-37
...@@ -22,6 +22,8 @@ step: Step,...@@ -22,6 +22,8 @@ step: Step,
22argv: ArrayList(Arg),22argv: ArrayList(Arg),
2323
24/// Set this to modify the current working directory24/// Set this to modify the current working directory
25/// TODO change this to a Build.Cache.Directory to better integrate with
26/// future child process cwd API.
25cwd: ?[]const u8,27cwd: ?[]const u8,
2628
27/// Override this field to modify the environment, or use setEnvironmentVariable29/// Override this field to modify the environment, or use setEnvironmentVariable
...@@ -89,7 +91,7 @@ pub const StdIo = union(enum) {...@@ -89,7 +91,7 @@ pub const StdIo = union(enum) {
89 expect_stderr_match: []const u8,91 expect_stderr_match: []const u8,
90 expect_stdout_exact: []const u8,92 expect_stdout_exact: []const u8,
91 expect_stdout_match: []const u8,93 expect_stdout_match: []const u8,
92 expect_term: std.ChildProcess.Term,94 expect_term: std.process.Child.Term,
93 };95 };
94};96};
9597
...@@ -401,7 +403,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -401,7 +403,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
401}403}
402404
403fn formatTerm(405fn formatTerm(
404 term: ?std.ChildProcess.Term,406 term: ?std.process.Child.Term,
405 comptime fmt: []const u8,407 comptime fmt: []const u8,
406 options: std.fmt.FormatOptions,408 options: std.fmt.FormatOptions,
407 writer: anytype,409 writer: anytype,
...@@ -417,11 +419,11 @@ fn formatTerm(...@@ -417,11 +419,11 @@ fn formatTerm(
417 try writer.writeAll("exited with any code");419 try writer.writeAll("exited with any code");
418 }420 }
419}421}
420fn fmtTerm(term: ?std.ChildProcess.Term) std.fmt.Formatter(formatTerm) {422fn fmtTerm(term: ?std.process.Child.Term) std.fmt.Formatter(formatTerm) {
421 return .{ .data = term };423 return .{ .data = term };
422}424}
423425
424fn termMatches(expected: ?std.ChildProcess.Term, actual: std.ChildProcess.Term) bool {426fn termMatches(expected: ?std.process.Child.Term, actual: std.process.Child.Term) bool {
425 return if (expected) |e| switch (e) {427 return if (expected) |e| switch (e) {
426 .Exited => |expected_code| switch (actual) {428 .Exited => |expected_code| switch (actual) {
427 .Exited => |actual_code| expected_code == actual_code,429 .Exited => |actual_code| expected_code == actual_code,
...@@ -453,10 +455,7 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool)...@@ -453,10 +455,7 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool)
453 try step.handleChildProcUnsupported(self.cwd, argv);455 try step.handleChildProcUnsupported(self.cwd, argv);
454 try Step.handleVerbose(step.owner, self.cwd, argv);456 try Step.handleVerbose(step.owner, self.cwd, argv);
455457
456 var stdout_bytes: ?[]const u8 = null;458 const result = spawnChildAndCollect(self, argv, has_side_effects) catch |err| term: {
457 var stderr_bytes: ?[]const u8 = null;
458
459 const term = spawnChildAndCollect(self, argv, &stdout_bytes, &stderr_bytes, has_side_effects) catch |err| term: {
460 if (err == error.InvalidExe) interpret: {459 if (err == error.InvalidExe) interpret: {
461 // TODO: learn the target from the binary directly rather than from460 // TODO: learn the target from the binary directly rather than from
462 // relying on it being a CompileStep. This will make this logic461 // relying on it being a CompileStep. This will make this logic
...@@ -571,11 +570,9 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool)...@@ -571,11 +570,9 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool)
571570
572 try Step.handleVerbose(step.owner, self.cwd, interp_argv.items);571 try Step.handleVerbose(step.owner, self.cwd, interp_argv.items);
573572
574 assert(stdout_bytes == null);573 break :term spawnChildAndCollect(self, interp_argv.items, has_side_effects) catch |e| {
575 assert(stderr_bytes == null);
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}", .{574 return step.fail("unable to spawn {s}: {s}", .{
578 interp_argv.items[0], @errorName(inner_err),575 interp_argv.items[0], @errorName(e),
579 });576 });
580 };577 };
581 }578 }
...@@ -586,7 +583,8 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool)...@@ -586,7 +583,8 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool)
586 switch (self.stdio) {583 switch (self.stdio) {
587 .check => |checks| for (checks.items) |check| switch (check) {584 .check => |checks| for (checks.items) |check| switch (check) {
588 .expect_stderr_exact => |expected_bytes| {585 .expect_stderr_exact => |expected_bytes| {
589 if (!mem.eql(u8, expected_bytes, stderr_bytes.?)) {586 assert(!result.stderr_null);
587 if (!mem.eql(u8, expected_bytes, result.stderr)) {
590 return step.fail(588 return step.fail(
591 \\589 \\
592 \\========= expected this stderr: =========590 \\========= expected this stderr: =========
...@@ -597,13 +595,14 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool)...@@ -597,13 +595,14 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool)
597 \\{s}595 \\{s}
598 , .{596 , .{
599 expected_bytes,597 expected_bytes,
600 stderr_bytes.?,598 result.stderr,
601 try Step.allocPrintCmd(arena, self.cwd, argv),599 try Step.allocPrintCmd(arena, self.cwd, argv),
602 });600 });
603 }601 }
604 },602 },
605 .expect_stderr_match => |match| {603 .expect_stderr_match => |match| {
606 if (mem.indexOf(u8, stderr_bytes.?, match) == null) {604 assert(!result.stderr_null);
605 if (mem.indexOf(u8, result.stderr, match) == null) {
607 return step.fail(606 return step.fail(
608 \\607 \\
609 \\========= expected to find in stderr: =========608 \\========= expected to find in stderr: =========
...@@ -614,13 +613,14 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool)...@@ -614,13 +613,14 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool)
614 \\{s}613 \\{s}
615 , .{614 , .{
616 match,615 match,
617 stderr_bytes.?,616 result.stderr,
618 try Step.allocPrintCmd(arena, self.cwd, argv),617 try Step.allocPrintCmd(arena, self.cwd, argv),
619 });618 });
620 }619 }
621 },620 },
622 .expect_stdout_exact => |expected_bytes| {621 .expect_stdout_exact => |expected_bytes| {
623 if (!mem.eql(u8, expected_bytes, stdout_bytes.?)) {622 assert(!result.stdout_null);
623 if (!mem.eql(u8, expected_bytes, result.stdout)) {
624 return step.fail(624 return step.fail(
625 \\625 \\
626 \\========= expected this stdout: =========626 \\========= expected this stdout: =========
...@@ -631,13 +631,14 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool)...@@ -631,13 +631,14 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool)
631 \\{s}631 \\{s}
632 , .{632 , .{
633 expected_bytes,633 expected_bytes,
634 stdout_bytes.?,634 result.stdout,
635 try Step.allocPrintCmd(arena, self.cwd, argv),635 try Step.allocPrintCmd(arena, self.cwd, argv),
636 });636 });
637 }637 }
638 },638 },
639 .expect_stdout_match => |match| {639 .expect_stdout_match => |match| {
640 if (mem.indexOf(u8, stdout_bytes.?, match) == null) {640 assert(!result.stdout_null);
641 if (mem.indexOf(u8, result.stdout, match) == null) {
641 return step.fail(642 return step.fail(
642 \\643 \\
643 \\========= expected to find in stdout: =========644 \\========= expected to find in stdout: =========
...@@ -648,15 +649,15 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool)...@@ -648,15 +649,15 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool)
648 \\{s}649 \\{s}
649 , .{650 , .{
650 match,651 match,
651 stdout_bytes.?,652 result.stdout,
652 try Step.allocPrintCmd(arena, self.cwd, argv),653 try Step.allocPrintCmd(arena, self.cwd, argv),
653 });654 });
654 }655 }
655 },656 },
656 .expect_term => |expected_term| {657 .expect_term => |expected_term| {
657 if (!termMatches(expected_term, term)) {658 if (!termMatches(expected_term, result.term)) {
658 return step.fail("the following command {} (expected {}):\n{s}", .{659 return step.fail("the following command {} (expected {}):\n{s}", .{
659 fmtTerm(term),660 fmtTerm(result.term),
660 fmtTerm(expected_term),661 fmtTerm(expected_term),
661 try Step.allocPrintCmd(arena, self.cwd, argv),662 try Step.allocPrintCmd(arena, self.cwd, argv),
662 });663 });
...@@ -664,24 +665,36 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool)...@@ -664,24 +665,36 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool)
664 },665 },
665 },666 },
666 else => {667 else => {
667 try step.handleChildProcessTerm(term, self.cwd, argv);668 try step.handleChildProcessTerm(result.term, self.cwd, argv);
668 },669 },
669 }670 }
670}671}
671672
673const ChildProcResult = struct {
674 // These use boolean flags instead of optionals as a workaround for
675 // https://github.com/ziglang/zig/issues/14783
676 stdout: []const u8,
677 stderr: []const u8,
678 stdout_null: bool,
679 stderr_null: bool,
680 term: std.process.Child.Term,
681};
682
672fn spawnChildAndCollect(683fn spawnChildAndCollect(
673 self: *RunStep,684 self: *RunStep,
674 argv: []const []const u8,685 argv: []const []const u8,
675 stdout_bytes: *?[]const u8,
676 stderr_bytes: *?[]const u8,
677 has_side_effects: bool,686 has_side_effects: bool,
678) !std.ChildProcess.Term {687) !ChildProcResult {
679 const b = self.step.owner;688 const b = self.step.owner;
680 const arena = b.allocator;689 const arena = b.allocator;
681 const cwd = if (self.cwd) |cwd| b.pathFromRoot(cwd) else b.build_root.path;
682690
683 var child = std.ChildProcess.init(argv, arena);691 var child = std.process.Child.init(argv, arena);
684 child.cwd = cwd;692 if (self.cwd) |cwd| {
693 child.cwd = b.pathFromRoot(cwd);
694 } else {
695 child.cwd = b.build_root.path;
696 child.cwd_dir = b.build_root.handle;
697 }
685 child.env_map = self.env_map orelse b.env_map;698 child.env_map = self.env_map orelse b.env_map;
686699
687 child.stdin_behavior = switch (self.stdio) {700 child.stdin_behavior = switch (self.stdio) {
...@@ -704,6 +717,13 @@ fn spawnChildAndCollect(...@@ -704,6 +717,13 @@ fn spawnChildAndCollect(
704 argv[0], @errorName(err),717 argv[0], @errorName(err),
705 });718 });
706719
720 // These are not optionals, as a workaround for
721 // https://github.com/ziglang/zig/issues/14783
722 var stdout_bytes: []const u8 = undefined;
723 var stderr_bytes: []const u8 = undefined;
724 var stdout_null = true;
725 var stderr_null = true;
726
707 if (child.stdout) |stdout| {727 if (child.stdout) |stdout| {
708 if (child.stderr) |stderr| {728 if (child.stderr) |stderr| {
709 var poller = std.io.poll(arena, enum { stdout, stderr }, .{729 var poller = std.io.poll(arena, enum { stdout, stderr }, .{
...@@ -719,26 +739,36 @@ fn spawnChildAndCollect(...@@ -719,26 +739,36 @@ fn spawnChildAndCollect(
719 return error.StderrStreamTooLong;739 return error.StderrStreamTooLong;
720 }740 }
721741
722 stdout_bytes.* = try poller.fifo(.stdout).toOwnedSlice();742 stdout_bytes = try poller.fifo(.stdout).toOwnedSlice();
723 stderr_bytes.* = try poller.fifo(.stderr).toOwnedSlice();743 stderr_bytes = try poller.fifo(.stderr).toOwnedSlice();
744 stdout_null = false;
745 stderr_null = false;
724 } else {746 } else {
725 stdout_bytes.* = try stdout.reader().readAllAlloc(arena, self.max_stdio_size);747 stdout_bytes = try stdout.reader().readAllAlloc(arena, self.max_stdio_size);
748 stdout_null = false;
726 }749 }
727 } else if (child.stderr) |stderr| {750 } else if (child.stderr) |stderr| {
728 stderr_bytes.* = try stderr.reader().readAllAlloc(arena, self.max_stdio_size);751 stderr_bytes = try stderr.reader().readAllAlloc(arena, self.max_stdio_size);
752 stderr_null = false;
729 }753 }
730754
731 if (stderr_bytes.*) |stderr| if (stderr.len > 0) {755 if (!stderr_null and stderr_bytes.len > 0) {
732 const stderr_is_diagnostic = switch (self.stdio) {756 const stderr_is_diagnostic = switch (self.stdio) {
733 .check => |checks| !checksContainStderr(checks.items),757 .check => |checks| !checksContainStderr(checks.items),
734 else => true,758 else => true,
735 };759 };
736 if (stderr_is_diagnostic) {760 if (stderr_is_diagnostic) {
737 try self.step.result_error_msgs.append(arena, stderr);761 try self.step.result_error_msgs.append(arena, stderr_bytes);
738 }762 }
739 };763 }
740764
741 return child.wait();765 return .{
766 .stdout = stdout_bytes,
767 .stderr = stderr_bytes,
768 .stdout_null = stdout_null,
769 .stderr_null = stderr_null,
770 .term = try child.wait(),
771 };
742}772}
743773
744fn addPathForDynLibs(self: *RunStep, artifact: *CompileStep) void {774fn addPathForDynLibs(self: *RunStep, artifact: *CompileStep) void {