Add a number of progress indicators to Cargo
authorAlex Crichton <alex@alexcrichton.com>
Mon, 16 Oct 2017 14:57:38 +0000 (07:57 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Mon, 6 Nov 2017 18:35:00 +0000 (10:35 -0800)
This commit is an attempt to stem the tide of "cargo is stuck updating the
registry" issues by giving a better indication as to what's happening in
long-running steps. The primary addition here is a `Progress` helper module
which prints and manages a progress bar for long-running operations like git
fetches, git checkouts, HTTP downloads, etc.

The second addition here is to print out when we've been stuck in resolve for
some time. We never really have a progress indicator for crate graph resolution
nor do we know when we're done updating sources. Instead we make a naive
assumption that when you've spent 0.5s in the resolution loop itself (not
updating deps) you're probably done updating dependencies and on to acutal
resolution. This will print out `Resolving crate graph...` and help inform that
Cargo is indeed not stuck looking at the registry, but rather it's churning away
in resolution.

src/cargo/core/resolver/mod.rs
src/cargo/core/shell.rs
src/cargo/ops/resolve.rs
src/cargo/sources/git/utils.rs
src/cargo/sources/registry/remote.rs
src/cargo/util/config.rs
src/cargo/util/mod.rs
src/cargo/util/progress.rs [new file with mode: 0644]
tests/resolve.rs

index 63f20a6cfba3ea054c17caa7e230628bb2f75bf8..f7f31b6a88f65d394082ac7a8dee89036f0d0fa5 100644 (file)
 
 use std::cmp::Ordering;
 use std::collections::{HashSet, HashMap, BinaryHeap, BTreeMap};
-use std::iter::FromIterator;
 use std::fmt;
+use std::iter::FromIterator;
 use std::ops::Range;
 use std::rc::Rc;
+use std::time::{Instant, Duration};
 
 use semver;
 use url::Url;
@@ -362,7 +363,8 @@ type Activations = HashMap<String, HashMap<SourceId, Vec<Summary>>>;
 pub fn resolve(summaries: &[(Summary, Method)],
                replacements: &[(PackageIdSpec, Dependency)],
                registry: &mut Registry,
-               config: Option<&Config>) -> CargoResult<Resolve> {
+               config: Option<&Config>,
+               print_warnings: bool) -> CargoResult<Resolve> {
     let cx = Context {
         resolve_graph: RcList::new(),
         resolve_features: HashMap::new(),
@@ -372,7 +374,7 @@ pub fn resolve(summaries: &[(Summary, Method)],
         warnings: RcList::new(),
     };
     let _p = profile::start("resolving");
-    let cx = activate_deps_loop(cx, registry, summaries)?;
+    let cx = activate_deps_loop(cx, registry, summaries, config)?;
 
     let mut resolve = Resolve {
         graph: cx.graph(),
@@ -398,11 +400,13 @@ pub fn resolve(summaries: &[(Summary, Method)],
 
     // If we have a shell, emit warnings about required deps used as feature.
     if let Some(config) = config {
-        let mut shell = config.shell();
-        let mut warnings = &cx.warnings;
-        while let Some(ref head) = warnings.head {
-            shell.warn(&head.0)?;
-            warnings = &head.1;
+        if print_warnings {
+            let mut shell = config.shell();
+            let mut warnings = &cx.warnings;
+            while let Some(ref head) = warnings.head {
+                shell.warn(&head.0)?;
+                warnings = &head.1;
+            }
         }
     }
 
@@ -420,7 +424,7 @@ fn activate(cx: &mut Context,
             parent: Option<&Summary>,
             candidate: Candidate,
             method: &Method)
-            -> CargoResult<Option<DepsFrame>> {
+            -> CargoResult<Option<(DepsFrame, Duration)>> {
     if let Some(parent) = parent {
         cx.resolve_graph.push(GraphNode::Link(parent.package_id().clone(),
                                            candidate.summary.package_id().clone()));
@@ -448,12 +452,13 @@ fn activate(cx: &mut Context,
         }
     };
 
+    let now = Instant::now();
     let deps = cx.build_deps(registry, &candidate, method)?;
-
-    Ok(Some(DepsFrame {
+    let frame = DepsFrame {
         parent: candidate,
         remaining_siblings: RcVecIter::new(Rc::new(deps)),
-    }))
+    };
+    Ok(Some((frame, now.elapsed())))
 }
 
 struct RcVecIter<T> {
@@ -580,7 +585,8 @@ impl RemainingCandidates {
 /// dependency graph, cx.resolve is returned.
 fn activate_deps_loop<'a>(mut cx: Context<'a>,
                           registry: &mut Registry,
-                          summaries: &[(Summary, Method)])
+                          summaries: &[(Summary, Method)],
+                          config: Option<&Config>)
                           -> CargoResult<Context<'a>> {
     // Note that a `BinaryHeap` is used for the remaining dependencies that need
     // activation. This heap is sorted such that the "largest value" is the most
@@ -594,10 +600,18 @@ fn activate_deps_loop<'a>(mut cx: Context<'a>,
     for &(ref summary, ref method) in summaries {
         debug!("initial activation: {}", summary.package_id());
         let candidate = Candidate { summary: summary.clone(), replace: None };
-        remaining_deps.extend(activate(&mut cx, registry, None, candidate,
-                                       method)?);
+        let res = activate(&mut cx, registry, None, candidate, method)?;
+        if let Some((frame, _)) = res {
+            remaining_deps.push(frame);
+        }
     }
 
+    let mut ticks = 0;
+    let start = Instant::now();
+    let time_to_print = Duration::from_millis(500);
+    let mut printed = false;
+    let mut deps_time = Duration::new(0, 0);
+
     // Main resolution loop, this is the workhorse of the resolution algorithm.
     //
     // You'll note that a few stacks are maintained on the side, which might
@@ -612,6 +626,28 @@ fn activate_deps_loop<'a>(mut cx: Context<'a>,
     // backtracking states where if we hit an error we can return to in order to
     // attempt to continue resolving.
     while let Some(mut deps_frame) = remaining_deps.pop() {
+
+        // If we spend a lot of time here (we shouldn't in most cases) then give
+        // a bit of a visual indicator as to what we're doing. Only enable this
+        // when stderr is a tty (a human is likely to be watching) to ensure we
+        // get deterministic output otherwise when observed by tools.
+        //
+        // Also note that we hit this loop a lot, so it's fairly performance
+        // sensitive. As a result try to defer a possibly expensive operation
+        // like `Instant::now` by only checking every N iterations of this loop
+        // to amortize the cost of the current time lookup.
+        ticks += 1;
+        if let Some(config) = config {
+            if config.shell().is_err_tty() &&
+                !printed &&
+                ticks % 1000 == 0 &&
+                start.elapsed() - deps_time > time_to_print
+            {
+                printed = true;
+                config.shell().status("Resolving", "dependency graph...")?;
+            }
+        }
+
         let frame = match deps_frame.remaining_siblings.next() {
             Some(sibling) => {
                 let parent = Summary::clone(&deps_frame.parent);
@@ -695,8 +731,11 @@ fn activate_deps_loop<'a>(mut cx: Context<'a>,
         };
         trace!("{}[{}]>{} trying {}", parent.name(), cur, dep.name(),
                candidate.summary.version());
-        remaining_deps.extend(activate(&mut cx, registry, Some(&parent),
-                              candidate, &method)?);
+        let res = activate(&mut cx, registry, Some(&parent), candidate, &method)?;
+        if let Some((frame, dur)) = res {
+            remaining_deps.push(frame);
+            deps_time += dur;
+        }
     }
 
     Ok(cx)
index 6911339d819b7e8518eac471407cfafc10f1e051..a26fd6c2c632fc6503d8fc43c26b6a27f29b37ae 100644 (file)
@@ -2,7 +2,7 @@ use std::fmt;
 use std::io::prelude::*;
 
 use atty;
-use termcolor::Color::{Green, Red, Yellow};
+use termcolor::Color::{Green, Red, Yellow, Cyan};
 use termcolor::{self, StandardStream, Color, ColorSpec, WriteColor};
 
 use util::errors::CargoResult;
@@ -28,13 +28,17 @@ pub struct Shell {
 impl fmt::Debug for Shell {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match &self.err {
-            &ShellOut::Write(_) => f.debug_struct("Shell")
-                .field("verbosity", &self.verbosity)
-                .finish(),
-            &ShellOut::Stream(_, color_choice) => f.debug_struct("Shell")
-                .field("verbosity", &self.verbosity)
-                .field("color_choice", &color_choice)
-                .finish()
+            &ShellOut::Write(_) => {
+                f.debug_struct("Shell")
+                    .field("verbosity", &self.verbosity)
+                    .finish()
+            }
+            &ShellOut::Stream { color_choice, .. } => {
+                f.debug_struct("Shell")
+                    .field("verbosity", &self.verbosity)
+                    .field("color_choice", &color_choice)
+                    .finish()
+            }
         }
     }
 }
@@ -44,7 +48,11 @@ enum ShellOut {
     /// A plain write object without color support
     Write(Box<Write>),
     /// Color-enabled stdio, with information on whether color should be used
-    Stream(StandardStream, ColorChoice),
+    Stream {
+        stream: StandardStream,
+        tty: bool,
+        color_choice: ColorChoice,
+    },
 }
 
 /// Whether messages should use color output
@@ -63,10 +71,11 @@ impl Shell {
     /// output.
     pub fn new() -> Shell {
         Shell {
-            err: ShellOut::Stream(
-                StandardStream::stderr(ColorChoice::CargoAuto.to_termcolor_color_choice()),
-                ColorChoice::CargoAuto,
-            ),
+            err: ShellOut::Stream {
+                stream: StandardStream::stderr(ColorChoice::CargoAuto.to_termcolor_color_choice()),
+                color_choice: ColorChoice::CargoAuto,
+                tty: atty::is(atty::Stream::Stderr),
+            },
             verbosity: Verbosity::Verbose,
         }
     }
@@ -83,7 +92,7 @@ impl Shell {
     /// messages follows without color.
     fn print(&mut self,
              status: &fmt::Display,
-             message: &fmt::Display,
+             message: Option<&fmt::Display>,
              color: Color,
              justified: bool) -> CargoResult<()> {
         match self.verbosity {
@@ -94,6 +103,22 @@ impl Shell {
         }
     }
 
+    /// Returns the width of the terminal in spaces, if any
+    pub fn err_width(&self) -> Option<usize> {
+        match self.err {
+            ShellOut::Stream { tty: true, .. } => imp::stderr_width(),
+            _ => None,
+        }
+    }
+
+    /// Returns whether stderr is a tty
+    pub fn is_err_tty(&self) -> bool {
+        match self.err {
+            ShellOut::Stream { tty, .. } => tty,
+            _ => false,
+        }
+    }
+
     /// Get a reference to the underlying writer
     pub fn err(&mut self) -> &mut Write {
         self.err.as_write()
@@ -103,7 +128,13 @@ impl Shell {
     pub fn status<T, U>(&mut self, status: T, message: U) -> CargoResult<()>
         where T: fmt::Display, U: fmt::Display
     {
-        self.print(&status, &message, Green, true)
+        self.print(&status, Some(&message), Green, true)
+    }
+
+    pub fn status_header<T>(&mut self, status: T) -> CargoResult<()>
+        where T: fmt::Display,
+    {
+        self.print(&status, None, Cyan, true)
     }
 
     /// Shortcut to right-align a status message.
@@ -113,7 +144,7 @@ impl Shell {
                                    color: Color) -> CargoResult<()>
         where T: fmt::Display, U: fmt::Display
     {
-        self.print(&status, &message, color, true)
+        self.print(&status, Some(&message), color, true)
     }
 
     /// Run the callback only if we are in verbose mode
@@ -138,14 +169,14 @@ impl Shell {
 
     /// Print a red 'error' message
     pub fn error<T: fmt::Display>(&mut self, message: T) -> CargoResult<()> {
-        self.print(&"error:", &message, Red, false)
+        self.print(&"error:", Some(&message), Red, false)
     }
 
     /// Print an amber 'warning' message
     pub fn warn<T: fmt::Display>(&mut self, message: T) -> CargoResult<()> {
         match self.verbosity {
             Verbosity::Quiet => Ok(()),
-            _ => self.print(&"warning:", &message, Yellow, false),
+            _ => self.print(&"warning:", Some(&message), Yellow, false),
         }
     }
 
@@ -161,7 +192,7 @@ impl Shell {
 
     /// Update the color choice (always, never, or auto) from a string.
     pub fn set_color_choice(&mut self, color: Option<&str>) -> CargoResult<()> {
-        if let ShellOut::Stream(ref mut err, ref mut cc) =  self.err {
+        if let ShellOut::Stream { ref mut stream, ref mut color_choice, .. } =  self.err {
             let cfg = match color {
                 Some("always") => ColorChoice::Always,
                 Some("never") => ColorChoice::Never,
@@ -172,8 +203,8 @@ impl Shell {
                 Some(arg) => bail!("argument for --color must be auto, always, or \
                                     never, but found `{}`", arg),
             };
-            *cc = cfg;
-            *err = StandardStream::stderr(cfg.to_termcolor_color_choice());
+            *color_choice = cfg;
+            *stream = StandardStream::stderr(cfg.to_termcolor_color_choice());
         }
         Ok(())
     }
@@ -184,7 +215,7 @@ impl Shell {
     /// has been set to something else.
     pub fn color_choice(&self) -> ColorChoice {
         match self.err {
-            ShellOut::Stream(_, cc) => cc,
+            ShellOut::Stream { color_choice, .. } => color_choice,
             ShellOut::Write(_) => ColorChoice::Never,
         }
     }
@@ -195,22 +226,25 @@ impl ShellOut {
     /// The status can be justified, in which case the max width that will right align is 12 chars.
     fn print(&mut self,
              status: &fmt::Display,
-             message: &fmt::Display,
+             message: Option<&fmt::Display>,
              color: Color,
              justified: bool) -> CargoResult<()> {
         match *self {
-            ShellOut::Stream(ref mut err, _) => {
-                err.reset()?;
-                err.set_color(ColorSpec::new()
+            ShellOut::Stream { ref mut stream, .. } => {
+                stream.reset()?;
+                stream.set_color(ColorSpec::new()
                                     .set_bold(true)
                                     .set_fg(Some(color)))?;
                 if justified {
-                    write!(err, "{:>12}", status)?;
+                    write!(stream, "{:>12}", status)?;
                 } else {
-                    write!(err, "{}", status)?;
+                    write!(stream, "{}", status)?;
+                }
+                stream.reset()?;
+                match message {
+                    Some(message) => write!(stream, " {}\n", message)?,
+                    None => write!(stream, " ")?,
                 }
-                err.reset()?;
-                write!(err, " {}\n", message)?;
             }
             ShellOut::Write(ref mut w) => {
                 if justified {
@@ -218,7 +252,10 @@ impl ShellOut {
                 } else {
                     write!(w, "{}", status)?;
                 }
-                write!(w, " {}\n", message)?;
+                match message {
+                    Some(message) => write!(w, " {}\n", message)?,
+                    None => write!(w, " ")?,
+                }
             }
         }
         Ok(())
@@ -227,7 +264,7 @@ impl ShellOut {
     /// Get this object as a `io::Write`.
     fn as_write(&mut self) -> &mut Write {
         match *self {
-            ShellOut::Stream(ref mut err, _) => err,
+            ShellOut::Stream { ref mut stream, .. } => stream,
             ShellOut::Write(ref mut w) => w,
         }
     }
@@ -249,3 +286,43 @@ impl ColorChoice {
         }
     }
 }
+
+#[cfg(unix)]
+mod imp {
+    use std::mem;
+
+    use libc;
+
+    pub fn stderr_width() -> Option<usize> {
+        unsafe {
+            let mut winsize: libc::winsize = mem::zeroed();
+            if libc::ioctl(libc::STDERR_FILENO, libc::TIOCGWINSZ, &mut winsize) < 0 {
+                return None
+            }
+            if winsize.ws_col > 0 {
+                Some(winsize.ws_col as usize)
+            } else {
+                None
+            }
+        }
+    }
+}
+
+#[cfg(windows)]
+mod imp {
+    use std::mem;
+
+    extern crate winapi;
+    extern crate kernel32;
+
+    pub fn stderr_width() -> Option<usize> {
+        unsafe {
+            let stdout = kernel32::GetStdHandle(winapi::STD_ERROR_HANDLE);
+            let mut csbi: winapi::CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();
+            if kernel32::GetConsoleScreenBufferInfo(stdout, &mut csbi) == 0 {
+                return None
+            }
+            Some((csbi.srWindow.Right - csbi.srWindow.Left) as usize)
+        }
+    }
+}
index f26eb8e9798f4f241be3b7cc4547d9dafbd9f4a0..2ccf3b394fa64c67ad37caa4bac9d8394de6d295 100644 (file)
@@ -257,15 +257,11 @@ pub fn resolve_with_previous<'a>(registry: &mut PackageRegistry,
         None => root_replace.to_vec(),
     };
 
-    let config = if warn {
-        Some(ws.config())
-    } else {
-        None
-    };
     let mut resolved = resolver::resolve(&summaries,
                                          &replace,
                                          registry,
-                                         config)?;
+                                         Some(ws.config()),
+                                         warn)?;
     resolved.register_used_patches(registry.patches());
     if let Some(previous) = previous {
         resolved.merge_from(previous)?;
index 505fd24a80c4d577f15ec3b883db8ede716288d7..ed2bc281c555b3de82447f4d37645ecf93ece6a6 100644 (file)
@@ -11,7 +11,7 @@ use serde::ser::{self, Serialize};
 use url::Url;
 
 use core::GitReference;
-use util::{ToUrl, internal, Config, network};
+use util::{ToUrl, internal, Config, network, Progress};
 use util::errors::{CargoResult, CargoResultExt, CargoError};
 
 #[derive(PartialEq, Clone, Debug)]
@@ -140,10 +140,6 @@ impl GitRemote {
 }
 
 impl GitDatabase {
-    fn path(&self) -> &Path {
-        &self.path
-    }
-
     pub fn copy_to(&self, rev: GitRevision, dest: &Path, cargo_config: &Config)
                    -> CargoResult<GitCheckout> {
         let checkout = match git2::Repository::open(dest) {
@@ -151,12 +147,12 @@ impl GitDatabase {
                 let mut checkout = GitCheckout::new(dest, self, rev, repo);
                 if !checkout.is_fresh() {
                     checkout.fetch(cargo_config)?;
-                    checkout.reset()?;
+                    checkout.reset(cargo_config)?;
                     assert!(checkout.is_fresh());
                 }
                 checkout
             }
-            Err(..) => GitCheckout::clone_into(dest, self, rev)?,
+            Err(..) => GitCheckout::clone_into(dest, self, rev, cargo_config)?,
         };
         checkout.update_submodules(cargo_config)?;
         Ok(checkout)
@@ -220,37 +216,26 @@ impl<'a> GitCheckout<'a> {
         }
     }
 
-    fn clone_into(into: &Path, database: &'a GitDatabase,
-                  revision: GitRevision)
+    fn clone_into(into: &Path,
+                  database: &'a GitDatabase,
+                  revision: GitRevision,
+                  config: &Config)
                   -> CargoResult<GitCheckout<'a>>
     {
-        let repo = GitCheckout::clone_repo(database.path(), into)?;
-        let checkout = GitCheckout::new(into, database, revision, repo);
-        checkout.reset()?;
-        Ok(checkout)
-    }
-
-    fn clone_repo(source: &Path, into: &Path) -> CargoResult<git2::Repository> {
         let dirname = into.parent().unwrap();
-
         fs::create_dir_all(&dirname).chain_err(|| {
             format!("Couldn't mkdir {}", dirname.display())
         })?;
-
         if fs::metadata(&into).is_ok() {
             fs::remove_dir_all(into).chain_err(|| {
                 format!("Couldn't rmdir {}", into.display())
             })?;
         }
-
-        let url = source.to_url()?;
-        let url = url.to_string();
-        let repo = git2::Repository::clone(&url, into)
-            .chain_err(|| {
-                internal(format!("failed to clone {} into {}", source.display(),
-                             into.display()))
-        })?;
-        Ok(repo)
+        let repo = git2::Repository::init(into)?;
+        let mut checkout = GitCheckout::new(into, database, revision, repo);
+        checkout.fetch(config)?;
+        checkout.reset(config)?;
+        Ok(checkout)
     }
 
     fn is_fresh(&self) -> bool {
@@ -271,7 +256,7 @@ impl<'a> GitCheckout<'a> {
         Ok(())
     }
 
-    fn reset(&self) -> CargoResult<()> {
+    fn reset(&self, config: &Config) -> CargoResult<()> {
         // If we're interrupted while performing this reset (e.g. we die because
         // of a signal) Cargo needs to be sure to try to check out this repo
         // again on the next go-round.
@@ -284,7 +269,7 @@ impl<'a> GitCheckout<'a> {
         let _ = fs::remove_file(&ok_file);
         info!("reset {} to {}", self.repo.path().display(), self.revision);
         let object = self.repo.find_object(self.revision.0, None)?;
-        self.repo.reset(&object, git2::ResetType::Hard, None)?;
+        reset(&self.repo, &object, config)?;
         File::create(ok_file)?;
         Ok(())
     }
@@ -339,7 +324,7 @@ impl<'a> GitCheckout<'a> {
                 Err(..) => {
                     let path = parent.workdir().unwrap().join(child.path());
                     let _ = fs::remove_dir_all(&path);
-                    git2::Repository::clone(url, &path)?
+                    git2::Repository::init(&path)?
                 }
             };
 
@@ -351,8 +336,8 @@ impl<'a> GitCheckout<'a> {
                                  child.name().unwrap_or(""), url))
             })?;
 
-            repo.find_object(head, None)
-                .and_then(|obj| { repo.reset(&obj, git2::ResetType::Hard, None)})?;
+            let obj = repo.find_object(head, None)?;
+            reset(&repo, &obj, cargo_config)?;
             update_submodules(&repo, cargo_config)
         }
     }
@@ -558,6 +543,18 @@ fn with_authentication<T, F>(url: &str, cfg: &git2::Config, mut f: F)
     })
 }
 
+fn reset(repo: &git2::Repository,
+         obj: &git2::Object,
+         config: &Config) -> CargoResult<()> {
+    let mut pb = Progress::new("Checkout", config);
+    let mut opts = git2::build::CheckoutBuilder::new();
+    opts.progress(|_, cur, max| {
+        drop(pb.tick(cur, max));
+    });
+    repo.reset(obj, git2::ResetType::Hard, Some(&mut opts))?;
+    Ok(())
+}
+
 pub fn fetch(repo: &mut git2::Repository,
              url: &Url,
              refspec: &str,
@@ -588,10 +585,15 @@ pub fn fetch(repo: &mut git2::Repository,
     maybe_gc_repo(repo)?;
 
     debug!("doing a fetch for {}", url);
+    let mut progress = Progress::new("Fetch", config);
     with_authentication(url.as_str(), &repo.config()?, |f| {
         let mut cb = git2::RemoteCallbacks::new();
         cb.credentials(f);
 
+        cb.transfer_progress(|stats| {
+            progress.tick(stats.indexed_objects(), stats.total_objects()).is_ok()
+        });
+
         // Create a local anonymous remote in the repository to fetch the url
         let mut remote = repo.remote_anonymous(url.as_str())?;
         let mut opts = git2::FetchOptions::new();
index 846226107147fa91d5492ac406bc66d7b92d36d4..e40b35034ba4706266fe9ac5e19c9eedd5fcc327 100644 (file)
@@ -3,6 +3,7 @@ use std::io::SeekFrom;
 use std::io::prelude::*;
 use std::mem;
 use std::path::Path;
+use std::str;
 
 use git2;
 use hex::ToHex;
@@ -14,7 +15,7 @@ use sources::git;
 use sources::registry::{RegistryData, RegistryConfig, INDEX_LOCK};
 use util::network;
 use util::{FileLock, Filesystem, LazyCell};
-use util::{Config, Sha256, ToUrl};
+use util::{Config, Sha256, ToUrl, Progress};
 use util::errors::{CargoErrorKind, CargoResult, CargoResultExt};
 
 pub struct RemoteRegistry<'cfg> {
@@ -222,8 +223,13 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> {
         network::with_retry(self.config, || {
             state = Sha256::new();
             body = Vec::new();
+            let mut pb = Progress::new("Fetch", self.config);
             {
+                handle.progress(true)?;
                 let mut handle = handle.transfer();
+                handle.progress_function(|dl_total, dl_cur, _, _| {
+                    pb.tick(dl_cur as usize, dl_total as usize).is_ok()
+                })?;
                 handle.write_function(|buf| {
                     state.update(buf);
                     body.extend_from_slice(buf);
index 5bf967b1af1e3826d831fa6401b60ec769ac9a86..f343a6cf7f6226e5d8b62b98e3d2568e62ec205c 100644 (file)
@@ -630,9 +630,11 @@ impl Config {
     }
 
     pub fn http(&self) -> CargoResult<&RefCell<Easy>> {
-        self.easy.get_or_try_init(|| {
+        let http = self.easy.get_or_try_init(|| {
             ops::http_handle(self).map(RefCell::new)
-        })
+        })?;
+        http.borrow_mut().reset();
+        Ok(http)
     }
 }
 
index 9c1c9c5e0642ddbd2317c3e79c14bcb2ff657fcc..601933136bc3b20c3a74224dc2ba1be23b56918b 100644 (file)
@@ -18,6 +18,7 @@ pub use self::to_semver::ToSemver;
 pub use self::to_url::ToUrl;
 pub use self::vcs::{GitRepo, HgRepo, PijulRepo, FossilRepo};
 pub use self::read2::read2;
+pub use self::progress::Progress;
 
 pub mod config;
 pub mod errors;
@@ -42,3 +43,4 @@ mod vcs;
 mod lazy_cell;
 mod flock;
 mod read2;
+mod progress;
diff --git a/src/cargo/util/progress.rs b/src/cargo/util/progress.rs
new file mode 100644 (file)
index 0000000..c4a3e03
--- /dev/null
@@ -0,0 +1,127 @@
+use std::cmp;
+use std::iter;
+use std::time::{Instant, Duration};
+
+use util::{Config, CargoResult};
+
+pub struct Progress<'cfg> {
+    state: Option<State<'cfg>>,
+}
+
+struct State<'cfg> {
+    config: &'cfg Config,
+    width: usize,
+    first: bool,
+    last_update: Instant,
+    name: String,
+    done: bool,
+}
+
+impl<'cfg> Progress<'cfg> {
+    pub fn new(name: &str, cfg: &'cfg Config) -> Progress<'cfg> {
+        Progress {
+            state: cfg.shell().err_width().map(|n| {
+                State {
+                    config: cfg,
+                    width: cmp::min(n, 80),
+                    first: true,
+                    last_update: Instant::now(),
+                    name: name.to_string(),
+                    done: false,
+                }
+            }),
+        }
+    }
+
+    pub fn tick(&mut self, cur: usize, max: usize) -> CargoResult<()> {
+        match self.state {
+            Some(ref mut s) => s.tick(cur, max),
+            None => Ok(())
+        }
+    }
+}
+
+impl<'cfg> State<'cfg> {
+    fn tick(&mut self, cur: usize, max: usize) -> CargoResult<()> {
+        if self.done {
+            return Ok(())
+        }
+
+        // Don't update too often as it can cause excessive performance loss
+        // just putting stuff onto the terminal. We also want to avoid
+        // flickering by not drawing anything that goes away too quickly. As a
+        // result we've got two branches here:
+        //
+        // 1. If we haven't drawn anything, we wait for a period of time to
+        //    actually start drawing to the console. This ensures that
+        //    short-lived operations don't flicker on the console. Currently
+        //    there's a 500ms delay to when we first draw something.
+        // 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(())
+            }
+        }
+        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 extra_len = stats.len() + 2 /* [ and ] */ + 15 /* status header */;
+        let display_width = match self.width.checked_sub(extra_len) {
+            Some(n) => n,
+            None => return Ok(()),
+        };
+        let mut string = String::from("[");
+        let hashes = display_width as f64 * pct;
+        let hashes = hashes as usize;
+
+        // Draw the `===>`
+        if hashes > 0 {
+            for _ in 0..hashes-1 {
+                string.push_str("=");
+            }
+            if cur == max {
+                self.done = true;
+                string.push_str("=");
+            } else {
+                string.push_str(">");
+            }
+        }
+
+        // Draw the empty space we have left to do
+        for _ in 0..(display_width - hashes) {
+            string.push_str(" ");
+        }
+        string.push_str("]");
+        string.push_str(&stats);
+
+        // 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)?;
+        write!(self.config.shell().err(), "{}\r", string)?;
+        Ok(())
+    }
+}
+
+fn clear(width: usize, config: &Config) {
+    let blank = iter::repeat(" ").take(width).collect::<String>();
+    drop(write!(config.shell().err(), "{}\r", blank));
+}
+
+impl<'cfg> Drop for State<'cfg> {
+    fn drop(&mut self) {
+        clear(self.width, self.config);
+    }
+}
index 31f7abaa6e41f95d1bb7f0853a68331f99d09a90..42a67dd37d0bd223f1801bb4a5a8f0d13d80b900 100644 (file)
@@ -34,7 +34,7 @@ fn resolve(pkg: &PackageId, deps: Vec<Dependency>, registry: &[Summary])
     let mut registry = MyRegistry(registry);
     let summary = Summary::new(pkg.clone(), deps, BTreeMap::new()).unwrap();
     let method = Method::Everything;
-    let resolve = resolver::resolve(&[(summary, method)], &[], &mut registry, None)?;
+    let resolve = resolver::resolve(&[(summary, method)], &[], &mut registry, None, false)?;
     let res = resolve.iter().cloned().collect();
     Ok(res)
 }