Skip to content

Commit

Permalink
Displays a one line progress of what crates are currently built.
Browse files Browse the repository at this point in the history
  • Loading branch information
kennytm committed Jun 28, 2018
1 parent 9e4845e commit 3dbe701
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 28 deletions.
39 changes: 27 additions & 12 deletions src/cargo/core/compiler/job_queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use core::{PackageId, Target};
use handle_error;
use util::{internal, profile, CargoResult, CargoResultExt, ProcessBuilder};
use util::{Config, DependencyQueue, Dirty, Fresh, Freshness};
use util::Progress;

use super::job::Job;
use super::{BuildContext, BuildPlan, CompileMode, Context, Kind, Unit};
Expand All @@ -28,7 +29,7 @@ pub struct JobQueue<'a> {
queue: DependencyQueue<Key<'a>, Vec<(Job, Freshness)>>,
tx: Sender<Message<'a>>,
rx: Receiver<Message<'a>>,
active: usize,
active: HashSet<Key<'a>>,
pending: HashMap<Key<'a>, PendingBuild>,
compiled: HashSet<&'a PackageId>,
documented: HashSet<&'a PackageId>,
Expand Down Expand Up @@ -98,7 +99,7 @@ impl<'a> JobQueue<'a> {
queue: DependencyQueue::new(),
tx,
rx,
active: 0,
active: HashSet::new(),
pending: HashMap::new(),
compiled: HashSet::new(),
documented: HashSet::new(),
Expand Down Expand Up @@ -180,6 +181,8 @@ impl<'a> JobQueue<'a> {
// successful and otherwise wait for pending work to finish if it failed
// and then immediately return.
let mut error = None;
let mut progress = Progress::new("Building", cx.bcx.config);
let queue_len = self.queue.len();
loop {
// Dequeue as much work as we can, learning about everything
// possible that can run. Note that this is also the point where we
Expand All @@ -196,7 +199,7 @@ impl<'a> JobQueue<'a> {
);
for (job, f) in jobs {
queue.push((key, job, f.combine(fresh)));
if self.active + queue.len() > 0 {
if !self.active.is_empty() || !queue.is_empty() {
jobserver_helper.request_token();
}
}
Expand All @@ -205,14 +208,14 @@ impl<'a> JobQueue<'a> {
// Now that we've learned of all possible work that we can execute
// try to spawn it so long as we've got a jobserver token which says
// we're able to perform some parallel work.
while error.is_none() && self.active < tokens.len() + 1 && !queue.is_empty() {
while error.is_none() && self.active.len() < tokens.len() + 1 && !queue.is_empty() {
let (key, job, fresh) = queue.remove(0);
self.run(key, fresh, job, cx.bcx.config, scope, build_plan)?;
}

// If after all that we're not actually running anything then we're
// done!
if self.active == 0 {
if self.active.is_empty() {
break;
}

Expand All @@ -221,9 +224,19 @@ impl<'a> JobQueue<'a> {
// jobserver interface is architected we may acquire a token that we
// don't actually use, and if this happens just relinquish it back
// to the jobserver itself.
tokens.truncate(self.active - 1);

match self.rx.recv().unwrap() {
tokens.truncate(self.active.len() - 1);

let count = queue_len - self.queue.len();
let mut active_names = self.active.iter().map(|key| match key.mode {
CompileMode::Doc { .. } => format!("{}(doc)", key.pkg.name()),
_ => key.pkg.name().to_string(),
}).collect::<Vec<_>>();
active_names.sort_unstable();
drop(progress.tick_now(count, queue_len, format!(": {}", active_names.join(", "))));
let event = self.rx.recv().unwrap();
progress.clear();

match event {
Message::Run(cmd) => {
cx.bcx
.config
Expand All @@ -245,8 +258,9 @@ impl<'a> JobQueue<'a> {
}
Message::Finish(key, result) => {
info!("end: {:?}", key);
self.active -= 1;
if self.active > 0 {

self.active.remove(&key);
if !self.active.is_empty() {
assert!(!tokens.is_empty());
drop(tokens.pop());
}
Expand All @@ -256,7 +270,7 @@ impl<'a> JobQueue<'a> {
let msg = "The following warnings were emitted during compilation:";
self.emit_warnings(Some(msg), &key, cx)?;

if self.active > 0 {
if !self.active.is_empty() {
error = Some(format_err!("build failed"));
handle_error(e, &mut *cx.bcx.config.shell());
cx.bcx.config.shell().warn(
Expand All @@ -274,6 +288,7 @@ impl<'a> JobQueue<'a> {
}
}
}
drop(progress);

let build_type = if self.is_release { "release" } else { "dev" };
// NOTE: This may be a bit inaccurate, since this may not display the
Expand Down Expand Up @@ -334,7 +349,7 @@ impl<'a> JobQueue<'a> {
) -> CargoResult<()> {
info!("start: {:?}", key);

self.active += 1;
self.active.insert(key);
*self.counts.get_mut(key.pkg).unwrap() -= 1;

let my_tx = self.tx.clone();
Expand Down
56 changes: 40 additions & 16 deletions src/cargo/util/progress.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::cmp;
use std::env;
use std::iter;
use std::time::{Duration, Instant};

use core::shell::Verbosity;
Expand All @@ -12,6 +11,7 @@ pub struct Progress<'cfg> {

struct State<'cfg> {
config: &'cfg Config,
max_width: usize,
width: usize,
first: bool,
last_update: Instant,
Expand All @@ -33,6 +33,7 @@ impl<'cfg> Progress<'cfg> {
Progress {
state: cfg.shell().err_width().map(|n| State {
config: cfg,
max_width: n,
width: cmp::min(n, 80),
first: true,
last_update: Instant::now(),
Expand All @@ -44,14 +45,27 @@ impl<'cfg> Progress<'cfg> {

pub fn tick(&mut self, cur: usize, max: usize) -> CargoResult<()> {
match self.state {
Some(ref mut s) => s.tick(cur, max),
Some(ref mut s) => s.tick(cur, max, String::new(), true),
None => Ok(()),
}
}

pub fn clear(&mut self) {
if let Some(ref mut s) = self.state {
clear(s.max_width, s.config);
}
}

pub fn tick_now(&mut self, cur: usize, max: usize, msg: String) -> CargoResult<()> {
match self.state {
Some(ref mut s) => s.tick(cur, max, msg, false),
None => Ok(()),
}
}
}

impl<'cfg> State<'cfg> {
fn tick(&mut self, cur: usize, max: usize) -> CargoResult<()> {
fn tick(&mut self, cur: usize, max: usize, msg: String, throttle: bool) -> CargoResult<()> {
if self.done {
return Ok(());
}
Expand All @@ -68,19 +82,21 @@ impl<'cfg> State<'cfg> {
// 2. If we've drawn something, then we rate limit ourselves to only
// draw to the console every so often. Currently there's a 100ms
// delay between updates.
if self.first {
let delay = Duration::from_millis(500);
if self.last_update.elapsed() < delay {
return Ok(());
}
self.first = false;
} else {
let interval = Duration::from_millis(100);
if self.last_update.elapsed() < interval {
return Ok(());
if throttle {
if self.first {
let delay = Duration::from_millis(500);
if self.last_update.elapsed() < delay {
return Ok(());
}
self.first = false;
} else {
let interval = Duration::from_millis(100);
if self.last_update.elapsed() < interval {
return Ok(());
}
}
self.last_update = Instant::now();
}
self.last_update = Instant::now();

// Render the percentage at the far right and then figure how long the
// progress bar is
Expand Down Expand Up @@ -116,6 +132,14 @@ impl<'cfg> State<'cfg> {
string.push_str("]");
string.push_str(&stats);

let avail_msg_len = self.max_width - self.width;
if avail_msg_len >= msg.len() + 3 {
string.push_str(&msg);
} else if avail_msg_len >= 4 {
string.push_str(&msg[..(avail_msg_len - 3)]);
string.push_str("...");
}

// Write out a pretty header, then the progress bar itself, and then
// return back to the beginning of the line for the next print.
self.config.shell().status_header(&self.name)?;
Expand All @@ -125,12 +149,12 @@ impl<'cfg> State<'cfg> {
}

fn clear(width: usize, config: &Config) {
let blank = iter::repeat(" ").take(width).collect::<String>();
let blank = " ".repeat(width);
drop(write!(config.shell().err(), "{}\r", blank));
}

impl<'cfg> Drop for State<'cfg> {
fn drop(&mut self) {
clear(self.width, self.config);
clear(self.max_width, self.config);
}
}

0 comments on commit 3dbe701

Please sign in to comment.