-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Displays a one line progress of what crates are currently built. #5620
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, ProgressStyle}; | ||
|
|
||
| use super::job::Job; | ||
| use super::{BuildContext, BuildPlan, CompileMode, Context, Kind, Unit}; | ||
|
|
@@ -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: Vec<Key<'a>>, | ||
| pending: HashMap<Key<'a>, PendingBuild>, | ||
| compiled: HashSet<&'a PackageId>, | ||
| documented: HashSet<&'a PackageId>, | ||
|
|
@@ -98,7 +99,7 @@ impl<'a> JobQueue<'a> { | |
| queue: DependencyQueue::new(), | ||
| tx, | ||
| rx, | ||
| active: 0, | ||
| active: Vec::new(), | ||
| pending: HashMap::new(), | ||
| compiled: HashSet::new(), | ||
| documented: HashSet::new(), | ||
|
|
@@ -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::with_style("Building", ProgressStyle::Ratio, 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 | ||
|
|
@@ -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(); | ||
| } | ||
| } | ||
|
|
@@ -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; | ||
| } | ||
|
|
||
|
|
@@ -221,9 +224,18 @@ 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 active_names = self.active.iter().map(|key| match key.mode { | ||
| CompileMode::Doc { .. } => format!("{}(doc)", key.pkg.name()), | ||
| _ => key.pkg.name().to_string(), | ||
| }).collect::<Vec<_>>(); | ||
| 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 | ||
|
|
@@ -245,8 +257,12 @@ impl<'a> JobQueue<'a> { | |
| } | ||
| Message::Finish(key, result) => { | ||
| info!("end: {:?}", key); | ||
| self.active -= 1; | ||
| if self.active > 0 { | ||
|
|
||
| // self.active.remove_item(&key); // <- switch to this when stabilized. | ||
| if let Some(pos) = self.active.iter().position(|k| *k == key) { | ||
|
||
| self.active.remove(pos); | ||
| } | ||
| if !self.active.is_empty() { | ||
| assert!(!tokens.is_empty()); | ||
| drop(tokens.pop()); | ||
| } | ||
|
|
@@ -256,7 +272,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( | ||
|
|
@@ -274,6 +290,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 | ||
|
|
@@ -334,7 +351,7 @@ impl<'a> JobQueue<'a> { | |
| ) -> CargoResult<()> { | ||
| info!("start: {:?}", key); | ||
|
|
||
| self.active += 1; | ||
| self.active.push(key); | ||
| *self.counts.get_mut(key.pkg).unwrap() -= 1; | ||
|
|
||
| let my_tx = self.tx.clone(); | ||
|
|
||
| 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; | ||
|
|
@@ -10,8 +9,15 @@ pub struct Progress<'cfg> { | |
| state: Option<State<'cfg>>, | ||
| } | ||
|
|
||
| pub enum ProgressStyle { | ||
| Percentage, | ||
| Ratio, | ||
| } | ||
|
|
||
| struct State<'cfg> { | ||
| config: &'cfg Config, | ||
| style: ProgressStyle, | ||
| max_width: usize, | ||
| width: usize, | ||
| first: bool, | ||
| last_update: Instant, | ||
|
|
@@ -20,7 +26,7 @@ struct State<'cfg> { | |
| } | ||
|
|
||
| impl<'cfg> Progress<'cfg> { | ||
| pub fn new(name: &str, cfg: &'cfg Config) -> Progress<'cfg> { | ||
| pub fn with_style(name: &str, style: ProgressStyle, cfg: &'cfg Config) -> Progress<'cfg> { | ||
| // report no progress when -q (for quiet) or TERM=dumb are set | ||
| let dumb = match env::var("TERM") { | ||
| Ok(term) => term == "dumb", | ||
|
|
@@ -33,6 +39,8 @@ impl<'cfg> Progress<'cfg> { | |
| Progress { | ||
| state: cfg.shell().err_width().map(|n| State { | ||
| config: cfg, | ||
| style, | ||
| max_width: n, | ||
| width: cmp::min(n, 80), | ||
| first: true, | ||
| last_update: Instant::now(), | ||
|
|
@@ -42,16 +50,33 @@ impl<'cfg> Progress<'cfg> { | |
| } | ||
| } | ||
|
|
||
| pub fn new(name: &str, cfg: &'cfg Config) -> Progress<'cfg> { | ||
| Self::with_style(name, ProgressStyle::Percentage, 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(()); | ||
| } | ||
|
|
@@ -68,25 +93,30 @@ 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 | ||
| let pct = (cur as f64) / (max as f64); | ||
| let pct = if !pct.is_finite() { 0.0 } else { pct }; | ||
| let stats = format!(" {:6.02}%", pct * 100.0); | ||
| let stats = match self.style { | ||
| ProgressStyle::Percentage => format!(" {:6.02}%", pct * 100.0), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: let’s move pct calculation inside the match arm to minimize scope?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @matklad The |
||
| ProgressStyle::Ratio => format!(" {}/{}", cur, max), | ||
| }; | ||
| let extra_len = stats.len() + 2 /* [ and ] */ + 15 /* status header */; | ||
| let display_width = match self.width.checked_sub(extra_len) { | ||
| Some(n) => n, | ||
|
|
@@ -116,6 +146,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)?; | ||
|
|
@@ -125,12 +163,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); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Micronit:
queue.len() - queueue_lenlooks funny. Perhaps, rename it tototal -self.que.len()?