Skip to content

Commit e804cd4

Browse files
committed
Auto merge of #143354 - Shourya742:2025-07-03-bye-bye-as_mut-command, r=Kobzol
Port streaming commands in bootstrap to `BootstrapCommand` and remove `as_command_mut` This PR adds streaming capabilities to BootstrapCommand and migrate existing command streaming scenario's used in bootstrap. r? `@Kobzol`
2 parents febb10d + 312de35 commit e804cd4

File tree

3 files changed

+76
-46
lines changed

3 files changed

+76
-46
lines changed

src/bootstrap/src/core/build_steps/compile.rs

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use std::ffi::OsStr;
1212
use std::io::BufReader;
1313
use std::io::prelude::*;
1414
use std::path::{Path, PathBuf};
15-
use std::process::Stdio;
1615
use std::{env, fs, str};
1716

1817
use serde_derive::Deserialize;
@@ -2507,7 +2506,6 @@ pub fn stream_cargo(
25072506
#[cfg(feature = "tracing")]
25082507
let _run_span = crate::trace_cmd!(cmd);
25092508

2510-
let cargo = cmd.as_command_mut();
25112509
// Instruct Cargo to give us json messages on stdout, critically leaving
25122510
// stderr as piped so we can get those pretty colors.
25132511
let mut message_format = if builder.config.json_output {
@@ -2519,27 +2517,24 @@ pub fn stream_cargo(
25192517
message_format.push_str(",json-diagnostic-");
25202518
message_format.push_str(s);
25212519
}
2522-
cargo.arg("--message-format").arg(message_format).stdout(Stdio::piped());
2520+
cmd.arg("--message-format").arg(message_format);
25232521

25242522
for arg in tail_args {
2525-
cargo.arg(arg);
2523+
cmd.arg(arg);
25262524
}
25272525

2528-
builder.verbose(|| println!("running: {cargo:?}"));
2526+
builder.verbose(|| println!("running: {cmd:?}"));
25292527

2530-
if builder.config.dry_run() {
2531-
return true;
2532-
}
2528+
let streaming_command = cmd.stream_capture_stdout(&builder.config.exec_ctx);
25332529

2534-
let mut child = match cargo.spawn() {
2535-
Ok(child) => child,
2536-
Err(e) => panic!("failed to execute command: {cargo:?}\nERROR: {e}"),
2530+
let Some(mut streaming_command) = streaming_command else {
2531+
return true;
25372532
};
25382533

25392534
// Spawn Cargo slurping up its JSON output. We'll start building up the
25402535
// `deps` array of all files it generated along with a `toplevel` array of
25412536
// files we need to probe for later.
2542-
let stdout = BufReader::new(child.stdout.take().unwrap());
2537+
let stdout = BufReader::new(streaming_command.stdout.take().unwrap());
25432538
for line in stdout.lines() {
25442539
let line = t!(line);
25452540
match serde_json::from_str::<CargoMessage<'_>>(&line) {
@@ -2556,13 +2551,14 @@ pub fn stream_cargo(
25562551
}
25572552

25582553
// Make sure Cargo actually succeeded after we read all of its stdout.
2559-
let status = t!(child.wait());
2554+
let status = t!(streaming_command.wait());
25602555
if builder.is_verbose() && !status.success() {
25612556
eprintln!(
2562-
"command did not execute successfully: {cargo:?}\n\
2557+
"command did not execute successfully: {cmd:?}\n\
25632558
expected success, got: {status}"
25642559
);
25652560
}
2561+
25662562
status.success()
25672563
}
25682564

src/bootstrap/src/utils/exec.rs

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ use std::fmt::{Debug, Formatter};
1313
use std::hash::Hash;
1414
use std::panic::Location;
1515
use std::path::Path;
16-
use std::process::{Child, Command, CommandArgs, CommandEnvs, ExitStatus, Output, Stdio};
16+
use std::process::{
17+
Child, ChildStderr, ChildStdout, Command, CommandArgs, CommandEnvs, ExitStatus, Output, Stdio,
18+
};
1719
use std::sync::{Arc, Mutex};
1820

1921
use build_helper::ci::CiEnv;
@@ -209,15 +211,14 @@ impl<'a> BootstrapCommand {
209211
exec_ctx.as_ref().start(self, OutputMode::Capture, OutputMode::Print)
210212
}
211213

212-
/// Provides access to the stdlib Command inside.
213-
/// FIXME: This function should be eventually removed from bootstrap.
214-
pub fn as_command_mut(&mut self) -> &mut Command {
215-
// We proactively mark this command as executed since we can't be certain how the returned
216-
// command will be handled. Caching must also be avoided here, as the inner command could be
217-
// modified externally without us being aware.
218-
self.mark_as_executed();
219-
self.do_not_cache();
220-
&mut self.command
214+
/// Spawn the command in background, while capturing and returning stdout, and printing stderr.
215+
/// Returns None in dry-mode
216+
#[track_caller]
217+
pub fn stream_capture_stdout(
218+
&'a mut self,
219+
exec_ctx: impl AsRef<ExecutionContext>,
220+
) -> Option<StreamingCommand> {
221+
exec_ctx.as_ref().stream(self, OutputMode::Capture, OutputMode::Print)
221222
}
222223

223224
/// Mark the command as being executed, disarming the drop bomb.
@@ -449,6 +450,12 @@ enum CommandState<'a> {
449450
},
450451
}
451452

453+
pub struct StreamingCommand {
454+
child: Child,
455+
pub stdout: Option<ChildStdout>,
456+
pub stderr: Option<ChildStderr>,
457+
}
458+
452459
#[must_use]
453460
pub struct DeferredCommand<'a> {
454461
state: CommandState<'a>,
@@ -617,6 +624,33 @@ impl ExecutionContext {
617624
}
618625
exit!(1);
619626
}
627+
628+
/// Spawns the command with configured stdout and stderr handling.
629+
///
630+
/// Returns None if in dry-run mode or Panics if the command fails to spawn.
631+
pub fn stream(
632+
&self,
633+
command: &mut BootstrapCommand,
634+
stdout: OutputMode,
635+
stderr: OutputMode,
636+
) -> Option<StreamingCommand> {
637+
command.mark_as_executed();
638+
if !command.run_in_dry_run && self.dry_run() {
639+
return None;
640+
}
641+
let cmd = &mut command.command;
642+
cmd.stdout(stdout.stdio());
643+
cmd.stderr(stderr.stdio());
644+
let child = cmd.spawn();
645+
let mut child = match child {
646+
Ok(child) => child,
647+
Err(e) => panic!("failed to execute command: {cmd:?}\nERROR: {e}"),
648+
};
649+
650+
let stdout = child.stdout.take();
651+
let stderr = child.stderr.take();
652+
Some(StreamingCommand { child, stdout, stderr })
653+
}
620654
}
621655

622656
impl AsRef<ExecutionContext> for ExecutionContext {
@@ -625,6 +659,12 @@ impl AsRef<ExecutionContext> for ExecutionContext {
625659
}
626660
}
627661

662+
impl StreamingCommand {
663+
pub fn wait(mut self) -> Result<ExitStatus, std::io::Error> {
664+
self.child.wait()
665+
}
666+
}
667+
628668
impl<'a> DeferredCommand<'a> {
629669
pub fn wait_for_output(self, exec_ctx: impl AsRef<ExecutionContext>) -> CommandOutput {
630670
match self.state {

src/bootstrap/src/utils/render_tests.rs

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
//! to reimplement all the rendering logic in this module because of that.
88
99
use std::io::{BufRead, BufReader, Read, Write};
10-
use std::process::{ChildStdout, Stdio};
10+
use std::process::ChildStdout;
1111
use std::time::Duration;
1212

1313
use termcolor::{Color, ColorSpec, WriteColor};
@@ -34,50 +34,44 @@ pub(crate) fn try_run_tests(
3434
cmd: &mut BootstrapCommand,
3535
stream: bool,
3636
) -> bool {
37-
if builder.config.dry_run() {
38-
cmd.mark_as_executed();
37+
if run_tests(builder, cmd, stream) {
3938
return true;
4039
}
4140

42-
if !run_tests(builder, cmd, stream) {
43-
if builder.fail_fast {
44-
crate::exit!(1);
45-
} else {
46-
builder.config.exec_ctx().add_to_delay_failure(format!("{cmd:?}"));
47-
false
48-
}
49-
} else {
50-
true
41+
if builder.fail_fast {
42+
crate::exit!(1);
5143
}
44+
45+
builder.config.exec_ctx().add_to_delay_failure(format!("{cmd:?}"));
46+
47+
false
5248
}
5349

5450
fn run_tests(builder: &Builder<'_>, cmd: &mut BootstrapCommand, stream: bool) -> bool {
55-
let cmd = cmd.as_command_mut();
56-
cmd.stdout(Stdio::piped());
57-
5851
builder.verbose(|| println!("running: {cmd:?}"));
5952

60-
let mut process = cmd.spawn().unwrap();
53+
let Some(mut streaming_command) = cmd.stream_capture_stdout(&builder.config.exec_ctx) else {
54+
return true;
55+
};
6156

6257
// This runs until the stdout of the child is closed, which means the child exited. We don't
6358
// run this on another thread since the builder is not Sync.
64-
let renderer = Renderer::new(process.stdout.take().unwrap(), builder);
59+
let renderer = Renderer::new(streaming_command.stdout.take().unwrap(), builder);
6560
if stream {
6661
renderer.stream_all();
6762
} else {
6863
renderer.render_all();
6964
}
7065

71-
let result = process.wait_with_output().unwrap();
72-
if !result.status.success() && builder.is_verbose() {
66+
let status = streaming_command.wait().unwrap();
67+
if !status.success() && builder.is_verbose() {
7368
println!(
7469
"\n\ncommand did not execute successfully: {cmd:?}\n\
75-
expected success, got: {}",
76-
result.status
70+
expected success, got: {status}",
7771
);
7872
}
7973

80-
result.status.success()
74+
status.success()
8175
}
8276

8377
struct Renderer<'a> {

0 commit comments

Comments
 (0)