Auto merge of #4568 - Metaswitch:alt-registry-publish, r=withoutboats
Add support for publish to optionally take the index that can be used
This form part of alternative-registries RFC-2141, it allows crates to optionally specify which registries the crate can be be published to.
@carols10cents, one thing that I am unsure about is if there is a plan for publish to still provide index, or for registry to be provided instead. I thought that your general view was that we should move away from the index file. If we do need to map allowed registries to the index then there will be a small amount of extra work required once #4506 is merged.
@withoutboats, happy for this to be merged into your branch if you want, the main reason I did not base it on your branch was due to tests not working on there yet.
diff --git a/src/bin/login.rs b/src/bin/login.rs
index 499980c..d8a0ad4 100644
--- a/src/bin/login.rs
+++ b/src/bin/login.rs
@@ -21,7 +21,7 @@
}
pub const USAGE: &'static str = "
-Save an api token from the registry locally
+Save an api token from the registry locally. If token is not specified, it will be read from stdin.
Usage:
cargo login [options] [<token>]
diff --git a/src/bin/yank.rs b/src/bin/yank.rs
index 64eb3a0..ce5dfb7 100644
--- a/src/bin/yank.rs
+++ b/src/bin/yank.rs
@@ -25,18 +25,18 @@
cargo yank [options] [<crate>]
Options:
- -h, --help Print this message
- --vers VERSION The version to yank or un-yank
- --undo Undo a yank, putting a version back into the index
- --index INDEX Registry index to yank from
- --token TOKEN API token to use when authenticating
- -v, --verbose ... Use verbose output (-vv very verbose/build.rs output)
- -q, --quiet No output printed to stdout
- --color WHEN Coloring: auto, always, never
- --frozen Require Cargo.lock and cache are up to date
- --locked Require Cargo.lock is up to date
- -Z FLAG ... Unstable (nightly-only) flags to Cargo
- --registry REGISTRY Registry to use
+ -h, --help Print this message
+ --vers VERSION The version to yank or un-yank
+ --undo Undo a yank, putting a version back into the index
+ --index INDEX Registry index to yank from
+ --token TOKEN API token to use when authenticating
+ -v, --verbose ... Use verbose output (-vv very verbose/build.rs output)
+ -q, --quiet No output printed to stdout
+ --color WHEN Coloring: auto, always, never
+ --frozen Require Cargo.lock and cache are up to date
+ --locked Require Cargo.lock is up to date
+ -Z FLAG ... Unstable (nightly-only) flags to Cargo
+ --registry REGISTRY Registry to use
The yank command removes a previously pushed crate's version from the server's
index. This command does not delete any data, and the crate will still be
diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs
index 63f20a6..bc299f3 100644
--- a/src/cargo/core/resolver/mod.rs
+++ b/src/cargo/core/resolver/mod.rs
@@ -47,10 +47,11 @@
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;
@@ -73,6 +74,7 @@
///
/// Each instance of `Resolve` also understands the full set of features used
/// for each package.
+#[derive(PartialEq)]
pub struct Resolve {
graph: Graph<PackageId>,
replacements: HashMap<PackageId, PackageId>,
@@ -362,7 +364,8 @@
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 +375,7 @@
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 +401,13 @@
// 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 +425,7 @@
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 +453,13 @@
}
};
+ 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 +586,8 @@
/// 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 +601,18 @@
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 +627,28 @@
// 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 +732,11 @@
};
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)
diff --git a/src/cargo/core/shell.rs b/src/cargo/core/shell.rs
index 6911339..ade0850 100644
--- a/src/cargo/core/shell.rs
+++ b/src/cargo/core/shell.rs
@@ -2,7 +2,7 @@
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 @@
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 @@
/// 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 @@
/// 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 @@
/// 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 @@
}
}
+ /// 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 @@
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 @@
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 @@
/// 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 @@
/// 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 @@
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 @@
/// 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 @@
/// 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)?;
}
- err.reset()?;
- write!(err, " {}\n", message)?;
+ stream.reset()?;
+ match message {
+ Some(message) => write!(stream, " {}\n", message)?,
+ None => write!(stream, " ")?,
+ }
}
ShellOut::Write(ref mut w) => {
if justified {
@@ -218,7 +252,10 @@
} else {
write!(w, "{}", status)?;
}
- write!(w, " {}\n", message)?;
+ match message {
+ Some(message) => write!(w, " {}\n", message)?,
+ None => write!(w, " ")?,
+ }
}
}
Ok(())
@@ -227,7 +264,7 @@
/// 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,50 @@
}
}
}
+
+#[cfg(any(target_os = "linux", target_os = "macos"))]
+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(all(unix, not(any(target_os = "linux", target_os = "macos"))))]
+mod imp {
+ pub fn stderr_width() -> Option<usize> {
+ 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)
+ }
+ }
+}
diff --git a/src/cargo/ops/cargo_generate_lockfile.rs b/src/cargo/ops/cargo_generate_lockfile.rs
index d07ee96..0d6ebef 100644
--- a/src/cargo/ops/cargo_generate_lockfile.rs
+++ b/src/cargo/ops/cargo_generate_lockfile.rs
@@ -1,5 +1,7 @@
use std::collections::{BTreeMap, HashSet};
+use termcolor::Color::{self, Cyan, Green, Red};
+
use core::PackageId;
use core::registry::PackageRegistry;
use core::{Resolve, SourceId, Workspace};
@@ -83,8 +85,8 @@
true)?;
// Summarize what is changing for the user.
- let print_change = |status: &str, msg: String| {
- opts.config.shell().status(status, msg)
+ let print_change = |status: &str, msg: String, color: Color| {
+ opts.config.shell().status_with_color(status, msg, color)
};
for (removed, added) in compare_dependency_graphs(&previous_resolve, &resolve) {
if removed.len() == 1 && added.len() == 1 {
@@ -94,13 +96,13 @@
} else {
format!("{} -> v{}", removed[0], added[0].version())
};
- print_change("Updating", msg)?;
+ print_change("Updating", msg, Green)?;
} else {
for package in removed.iter() {
- print_change("Removing", format!("{}", package))?;
+ print_change("Removing", format!("{}", package), Red)?;
}
for package in added.iter() {
- print_change("Adding", format!("{}", package))?;
+ print_change("Adding", format!("{}", package), Cyan)?;
}
}
}
diff --git a/src/cargo/ops/cargo_run.rs b/src/cargo/ops/cargo_run.rs
index 3a4e7f6..c20d7bf 100644
--- a/src/cargo/ops/cargo_run.rs
+++ b/src/cargo/ops/cargo_run.rs
@@ -17,7 +17,7 @@
0 => ws.current()?,
1 => ws.members()
.find(|pkg| pkg.name() == xs[0])
- .ok_or_else(||
+ .ok_or_else(||
CargoError::from(
format!("package `{}` is not a member of the workspace", xs[0]))
)?,
@@ -25,25 +25,28 @@
}
};
- let mut bins = pkg.manifest().targets().iter().filter(|a| {
+ let bins: Vec<_> = pkg.manifest().targets().iter().filter(|a| {
!a.is_lib() && !a.is_custom_build() && if !options.filter.is_specific() {
a.is_bin()
} else {
options.filter.matches(a)
}
- });
- if bins.next().is_none() {
+ })
+ .map(|bin| bin.name())
+ .collect();
+
+ if bins.len() == 0 {
if !options.filter.is_specific() {
bail!("a bin target must be available for `cargo run`")
} else {
// this will be verified in cargo_compile
}
}
- if bins.next().is_some() {
+ if bins.len() > 1 {
if !options.filter.is_specific() {
bail!("`cargo run` requires that a project only have one \
executable; use the `--bin` option to specify which one \
- to run")
+ to run\navailable binaries: {}", bins.join(", "))
} else {
bail!("`cargo run` can run at most one executable, but \
multiple were specified")
diff --git a/src/cargo/ops/cargo_rustc/output_depinfo.rs b/src/cargo/ops/cargo_rustc/output_depinfo.rs
index 0a0644d..b07b299 100644
--- a/src/cargo/ops/cargo_rustc/output_depinfo.rs
+++ b/src/cargo/ops/cargo_rustc/output_depinfo.rs
@@ -19,23 +19,32 @@
relpath.to_str().ok_or_else(|| internal("path not utf-8")).map(|f| f.replace(" ", "\\ "))
}
-fn add_deps_for_unit<'a, 'b>(deps: &mut HashSet<PathBuf>, context: &mut Context<'a, 'b>,
- unit: &Unit<'a>, visited: &mut HashSet<Unit<'a>>) -> CargoResult<()>
+fn add_deps_for_unit<'a, 'b>(
+ deps: &mut HashSet<PathBuf>,
+ context: &mut Context<'a, 'b>,
+ unit: &Unit<'a>,
+ visited: &mut HashSet<Unit<'a>>,
+)
+ -> CargoResult<()>
{
if !visited.insert(*unit) {
return Ok(());
}
- // Add dependencies from rustc dep-info output (stored in fingerprint directory)
- let dep_info_loc = fingerprint::dep_info_loc(context, unit);
- if let Some(paths) = fingerprint::parse_dep_info(&dep_info_loc)? {
- for path in paths {
- deps.insert(path);
+ // units representing the execution of a build script don't actually
+ // generate a dep info file, so we just keep on going below
+ if !unit.profile.run_custom_build {
+ // Add dependencies from rustc dep-info output (stored in fingerprint directory)
+ let dep_info_loc = fingerprint::dep_info_loc(context, unit);
+ if let Some(paths) = fingerprint::parse_dep_info(&dep_info_loc)? {
+ for path in paths {
+ deps.insert(path);
+ }
+ } else {
+ debug!("can't find dep_info for {:?} {:?}",
+ unit.pkg.package_id(), unit.profile);
+ return Err(internal("dep_info missing"));
}
- } else {
- debug!("can't find dep_info for {:?} {:?}",
- unit.pkg.package_id(), unit.profile);
- return Err(internal("dep_info missing"));
}
// Add rerun-if-changed dependencies
diff --git a/src/cargo/ops/lockfile.rs b/src/cargo/ops/lockfile.rs
index 0a7aef5..7368bbf 100644
--- a/src/cargo/ops/lockfile.rs
+++ b/src/cargo/ops/lockfile.rs
@@ -69,7 +69,7 @@
// If the lockfile contents haven't changed so don't rewrite it. This is
// helpful on read-only filesystems.
if let Ok(orig) = orig {
- if are_equal_lockfiles(orig, &out, ws.config().lock_update_allowed()) {
+ if are_equal_lockfiles(orig, &out, ws) {
return Ok(())
}
}
@@ -91,20 +91,25 @@
})
}
-fn are_equal_lockfiles(mut orig: String, current: &str, lock_update_allowed: bool) -> bool {
+fn are_equal_lockfiles(mut orig: String, current: &str, ws: &Workspace) -> bool {
if has_crlf_line_endings(&orig) {
orig = orig.replace("\r\n", "\n");
}
- // Old lockfiles have unused `[root]` section,
- // just ignore it if we are in the `--frozen` mode.
- if !lock_update_allowed && orig.starts_with("[root]") {
- orig = orig.replacen("[root]", "[[package]]", 1);
- match (orig.parse::<toml::Value>(), current.parse::<toml::Value>()) {
- (Ok(ref a), Ok(ref b)) if a == b => return true,
- _ => {}
+ // If we want to try and avoid updating the lockfile, parse both and
+ // compare them; since this is somewhat expensive, don't do it in the
+ // common case where we can update lockfiles.
+ if !ws.config().lock_update_allowed() {
+ let res: CargoResult<bool> = (|| {
+ let old: resolver::EncodableResolve = toml::from_str(&orig)?;
+ let new: resolver::EncodableResolve = toml::from_str(current)?;
+ Ok(old.into_resolve(ws)? == new.into_resolve(ws)?)
+ })();
+ if let Ok(true) = res {
+ return true;
}
}
+
current == orig
}
diff --git a/src/cargo/ops/resolve.rs b/src/cargo/ops/resolve.rs
index f26eb8e..2ccf3b3 100644
--- a/src/cargo/ops/resolve.rs
+++ b/src/cargo/ops/resolve.rs
@@ -257,15 +257,11 @@
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)?;
diff --git a/src/cargo/sources/git/utils.rs b/src/cargo/sources/git/utils.rs
index 505fd24..ed2bc28 100644
--- a/src/cargo/sources/git/utils.rs
+++ b/src/cargo/sources/git/utils.rs
@@ -11,7 +11,7 @@
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 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 @@
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 @@
}
}
- 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 @@
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 @@
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 @@
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 @@
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 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 @@
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();
diff --git a/src/cargo/sources/registry/remote.rs b/src/cargo/sources/registry/remote.rs
index 8462261..e40b350 100644
--- a/src/cargo/sources/registry/remote.rs
+++ b/src/cargo/sources/registry/remote.rs
@@ -3,6 +3,7 @@
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::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 @@
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);
diff --git a/src/cargo/util/config.rs b/src/cargo/util/config.rs
index 5bf967b..f343a6c 100644
--- a/src/cargo/util/config.rs
+++ b/src/cargo/util/config.rs
@@ -630,9 +630,11 @@
}
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)
}
}
diff --git a/src/cargo/util/mod.rs b/src/cargo/util/mod.rs
index 9c1c9c5..6019331 100644
--- a/src/cargo/util/mod.rs
+++ b/src/cargo/util/mod.rs
@@ -18,6 +18,7 @@
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 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
index 0000000..c4a3e03
--- /dev/null
+++ b/src/cargo/util/progress.rs
@@ -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);
+ }
+}
diff --git a/src/doc/book/book.toml b/src/doc/book/book.toml
index 1b84b29..1f21e1e 100644
--- a/src/doc/book/book.toml
+++ b/src/doc/book/book.toml
@@ -1,2 +1,2 @@
-title = "The Cargo Manual"
+title = "The Cargo Book"
author = "Alex Crichton, Steve Klabnik and Carol Nichols, with Contributions from the Rust Community"
diff --git a/src/doc/book/src/faq.md b/src/doc/book/src/faq.md
index 7f13573..7ee2d2b 100644
--- a/src/doc/book/src/faq.md
+++ b/src/doc/book/src/faq.md
@@ -74,7 +74,7 @@
dependencies][target-deps], and we plan to support more per-platform
configuration in `Cargo.toml` in the future.
-[target-deps]: reference/manifest.html#the-dependencies-section
+[target-deps]: reference/specifying-dependencies.html#platform-specific-dependencies
In the longer-term, we’re looking at ways to conveniently cross-compile
projects using Cargo.
diff --git a/src/doc/book/src/index.md b/src/doc/book/src/index.md
index 3de0fc1..e3fc99c 100644
--- a/src/doc/book/src/index.md
+++ b/src/doc/book/src/index.md
@@ -1,15 +1,15 @@
-# The Cargo Manual
+# The Cargo Book

Cargo is the [Rust] *package manager*. Cargo downloads your Rust project’s
dependencies, compiles your project, makes packages, and upload them to
-[crates.io], the Rust *package registry*.
+[crates.io], the Rust community’s *package registry*.
### Sections
-**[Getting Started](getting-started.html)**
+**[Getting Started](getting-started/index.html)**
To get started with Cargo, install Cargo (and Rust) and set up your first crate.
diff --git a/src/doc/book/src/reference/specifying-dependencies.md b/src/doc/book/src/reference/specifying-dependencies.md
index b4f81fa..e58fa91 100644
--- a/src/doc/book/src/reference/specifying-dependencies.md
+++ b/src/doc/book/src/reference/specifying-dependencies.md
@@ -384,7 +384,7 @@
and you can find [more documentation about this configuration][config-docs].
Inside of `.cargo/config` you'll specify a key called `paths`:
-[config-docs]: config.html
+[config-docs]: reference/config.html
```toml
paths = ["/path/to/uuid"]
diff --git a/tests/dep-info.rs b/tests/dep-info.rs
index 8b060aa..f2f1f82 100644
--- a/tests/dep-info.rs
+++ b/tests/dep-info.rs
@@ -31,6 +31,7 @@
name = "ex"
crate-type = ["lib"]
"#)
+ .file("build.rs", "fn main() {}")
.file("src/lib.rs", "")
.file("examples/ex.rs", "")
.build();
diff --git a/tests/lockfile-compat.rs b/tests/lockfile-compat.rs
index 3f887c9..0c90fd4 100644
--- a/tests/lockfile-compat.rs
+++ b/tests/lockfile-compat.rs
@@ -22,24 +22,24 @@
let expected_lockfile =
r#"[[package]]
-name = "bar"
+name = "foo"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "zzz"
version = "0.0.1"
dependencies = [
"foo 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
-[[package]]
-name = "foo"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
[metadata]
"checksum foo 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "[..]"
"#;
let old_lockfile =
r#"[root]
-name = "bar"
+name = "zzz"
version = "0.0.1"
dependencies = [
"foo 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -54,7 +54,7 @@
let p = project("bar")
.file("Cargo.toml", r#"
[project]
- name = "bar"
+ name = "zzz"
version = "0.0.1"
authors = []
@@ -83,7 +83,7 @@
let old_lockfile =
r#"[root]
-name = "bar"
+name = "zzz"
version = "0.0.1"
dependencies = [
"foo 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -101,7 +101,7 @@
let p = project("bar")
.file("Cargo.toml", r#"
[project]
- name = "bar"
+ name = "zzz"
version = "0.0.1"
authors = []
diff --git a/tests/required-features.rs b/tests/required-features.rs
index 0f71d6d..233f7f6 100644
--- a/tests/required-features.rs
+++ b/tests/required-features.rs
@@ -996,5 +996,5 @@
assert_that(p.cargo("run"),
execs().with_status(101).with_stderr("\
error: `cargo run` requires that a project only have one executable; \
-use the `--bin` option to specify which one to run"));
+use the `--bin` option to specify which one to run\navailable binaries: foo1, foo2"));
}
diff --git a/tests/resolve.rs b/tests/resolve.rs
index 31f7aba..42a67dd 100644
--- a/tests/resolve.rs
+++ b/tests/resolve.rs
@@ -34,7 +34,7 @@
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)
}
diff --git a/tests/run.rs b/tests/run.rs
index 449c91c..efa107c 100644
--- a/tests/run.rs
+++ b/tests/run.rs
@@ -256,7 +256,7 @@
execs().with_status(101)
.with_stderr("[ERROR] `cargo run` requires that a project only \
have one executable; use the `--bin` option \
- to specify which one to run\n"));
+ to specify which one to run\navailable binaries: [..]\n"));
}
#[test]
@@ -278,7 +278,7 @@
execs().with_status(101)
.with_stderr("[ERROR] `cargo run` requires that a project only \
have one executable; use the `--bin` option \
- to specify which one to run\n"));
+ to specify which one to run\navailable binaries: [..]\n"));
}
#[test]