Skip to content

Commit 26690d3

Browse files
committed
Stream build script output to the console
This commit alters Cargo's behavior when the `-vv` option is passed (two verbose flags) to stream output of all build scripts to the console. Cargo makes not attempt to prevent interleaving or indicate *which* build script is producing output, rather it simply forwards all output to one to the console. Cargo still acts as a middle-man, capturing the output, to parse build script output and interpret the results. The parsing is still deferred to completion but the stream output happens while the build script is running. On Unix this is implemented via `select` and on Windows this is implemented via IOCP. Closes #1106
1 parent 805dfe4 commit 26690d3

File tree

10 files changed

+442
-53
lines changed

10 files changed

+442
-53
lines changed

Cargo.lock

Lines changed: 38 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ glob = "0.2"
3333
kernel32-sys = "0.2"
3434
libc = "0.2"
3535
log = "0.3"
36+
miow = "0.1"
3637
num_cpus = "0.2"
3738
regex = "0.1"
3839
rustc-serialize = "0.3"

src/cargo/ops/cargo_rustc/custom_build.rs

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ use std::fs;
33
use std::path::{PathBuf, Path};
44
use std::str;
55
use std::sync::{Mutex, Arc};
6+
use std::process::{Stdio, Output};
67

78
use core::PackageId;
89
use util::{CargoResult, Human};
910
use util::{internal, ChainError, profile, paths};
10-
use util::Freshness;
11+
use util::{Freshness, ProcessBuilder, read2};
12+
use util::errors::{process_error, ProcessError};
1113

1214
use super::job::Work;
15+
use super::job_queue::JobState;
1316
use super::{fingerprint, Kind, Context, Unit};
1417
use super::CommandType;
1518

@@ -159,14 +162,12 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>)
159162
try!(fs::create_dir_all(&cx.layout(unit.pkg, Kind::Host).build(unit.pkg)));
160163
try!(fs::create_dir_all(&cx.layout(unit.pkg, unit.kind).build(unit.pkg)));
161164

162-
let exec_engine = cx.exec_engine.clone();
163-
164165
// Prepare the unit of "dirty work" which will actually run the custom build
165166
// command.
166167
//
167168
// Note that this has to do some extra work just before running the command
168169
// to determine extra environment variables and such.
169-
let dirty = Work::new(move |desc_tx| {
170+
let dirty = Work::new(move |state| {
170171
// Make sure that OUT_DIR exists.
171172
//
172173
// If we have an old build directory, then just move it into place,
@@ -203,8 +204,9 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>)
203204
}
204205

205206
// And now finally, run the build command itself!
206-
desc_tx.send(p.to_string()).ok();
207-
let output = try!(exec_engine.exec_with_output(p).map_err(|mut e| {
207+
state.running(&p);
208+
let cmd = p.into_process_builder();
209+
let output = try!(stream_output(state, &cmd).map_err(|mut e| {
208210
e.desc = format!("failed to run custom build command for `{}`\n{}",
209211
pkg_name, e.desc);
210212
Human(e)
@@ -438,3 +440,55 @@ pub fn build_map<'b, 'cfg>(cx: &mut Context<'b, 'cfg>,
438440
}
439441
}
440442
}
443+
444+
fn stream_output(state: &JobState, cmd: &ProcessBuilder)
445+
-> Result<Output, ProcessError> {
446+
let mut stdout = Vec::new();
447+
let mut stderr = Vec::new();
448+
449+
let status = try!((|| {
450+
let mut cmd = cmd.build_command();
451+
cmd.stdout(Stdio::piped())
452+
.stderr(Stdio::piped())
453+
.stdin(Stdio::null());
454+
let mut child = try!(cmd.spawn());
455+
let out = child.stdout.take().unwrap();
456+
let err = child.stderr.take().unwrap();
457+
458+
try!(read2(out, err, &mut |is_out, data, eof| {
459+
let idx = if eof {
460+
data.len()
461+
} else {
462+
match data.iter().rposition(|b| *b == b'\n') {
463+
Some(i) => i + 1,
464+
None => return,
465+
}
466+
};
467+
let data = data.drain(..idx);
468+
let dst = if is_out {&mut stdout} else {&mut stderr};
469+
let start = dst.len();
470+
dst.extend(data);
471+
let s = String::from_utf8_lossy(&dst[start..]);
472+
if is_out {
473+
state.stdout(&s);
474+
} else {
475+
state.stderr(&s);
476+
}
477+
}));
478+
child.wait()
479+
})().map_err(|e| {
480+
let msg = format!("could not exeute process {}", cmd);
481+
process_error(&msg, Some(e), None, None)
482+
}));
483+
let output = Output {
484+
stdout: stdout,
485+
stderr: stderr,
486+
status: status,
487+
};
488+
if !output.status.success() {
489+
let msg = format!("process didn't exit successfully: {}", cmd);
490+
Err(process_error(&msg, None, Some(&status), Some(&output)))
491+
} else {
492+
Ok(output)
493+
}
494+
}

src/cargo/ops/cargo_rustc/job.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
use std::sync::mpsc::Sender;
21
use std::fmt;
32

43
use util::{CargoResult, Fresh, Dirty, Freshness};
4+
use super::job_queue::JobState;
55

66
pub struct Job { dirty: Work, fresh: Work }
77

88
/// Each proc should send its description before starting.
99
/// It should send either once or close immediately.
1010
pub struct Work {
11-
inner: Box<FnBox<Sender<String>, CargoResult<()>> + Send>,
11+
inner: Box<for <'a, 'b> FnBox<&'a JobState<'b>, CargoResult<()>> + Send>,
1212
}
1313

1414
trait FnBox<A, R> {
@@ -23,7 +23,7 @@ impl<A, R, F: FnOnce(A) -> R> FnBox<A, R> for F {
2323

2424
impl Work {
2525
pub fn new<F>(f: F) -> Work
26-
where F: FnOnce(Sender<String>) -> CargoResult<()> + Send + 'static
26+
where F: FnOnce(&JobState) -> CargoResult<()> + Send + 'static
2727
{
2828
Work { inner: Box::new(f) }
2929
}
@@ -32,14 +32,14 @@ impl Work {
3232
Work::new(|_| Ok(()))
3333
}
3434

35-
pub fn call(self, tx: Sender<String>) -> CargoResult<()> {
35+
pub fn call(self, tx: &JobState) -> CargoResult<()> {
3636
self.inner.call_box(tx)
3737
}
3838

3939
pub fn then(self, next: Work) -> Work {
40-
Work::new(move |tx| {
41-
try!(self.call(tx.clone()));
42-
next.call(tx)
40+
Work::new(move |state| {
41+
try!(self.call(state));
42+
next.call(state)
4343
})
4444
}
4545
}
@@ -52,10 +52,10 @@ impl Job {
5252

5353
/// Consumes this job by running it, returning the result of the
5454
/// computation.
55-
pub fn run(self, fresh: Freshness, tx: Sender<String>) -> CargoResult<()> {
55+
pub fn run(self, fresh: Freshness, state: &JobState) -> CargoResult<()> {
5656
match fresh {
57-
Fresh => self.fresh.call(tx),
58-
Dirty => self.dirty.call(tx),
57+
Fresh => self.fresh.call(state),
58+
Dirty => self.dirty.call(state),
5959
}
6060
}
6161
}

0 commit comments

Comments
 (0)