}
/// Returns true if the package (`sum`) can fulfill this dependency request.
- pub fn matches_ignoring_source(&self, sum: &Summary) -> bool {
- self.name() == sum.package_id().name()
- && self.version_req().matches(sum.package_id().version())
+ pub fn matches_ignoring_source(&self, id: &PackageId) -> bool {
+ self.name() == id.name() && self.version_req().matches(id.version())
}
/// Returns true if the package (`id`) can fulfill this dependency request.
use core::{Dependency, Manifest, PackageId, SourceId, Target};
use core::{SourceMap, Summary};
use core::interning::InternedString;
-use ops;
use util::{internal, lev_distance, Config};
use util::errors::{CargoResult, CargoResultExt};
patches.extend(
extra
.iter()
- .filter(|s| dep.matches_ignoring_source(s))
+ .filter(|s| dep.matches_ignoring_source(s.package_id()))
.cloned(),
);
}
use serde::de;
use core::{Dependency, Package, PackageId, SourceId, Workspace};
-use util::{internal, Config, Graph};
+use util::{internal, Graph};
use util::errors::{CargoError, CargoResult, CargoResultExt};
use super::Resolve;
}
}
-fn build_path_deps(ws: &Workspace) -> (HashMap<String, SourceId>) {
+fn build_path_deps(ws: &Workspace) -> HashMap<String, SourceId> {
// If a crate is *not* a path source, then we're probably in a situation
// such as `cargo install` with a lock file from a remote dependency. In
// that case we don't need to fixup any path dependencies (as they're not
type Activations = HashMap<(InternedString, SourceId), Rc<Vec<Summary>>>;
/// Builds the list of all packages required to build the first argument.
+///
+/// * `summaries` - the list of package summaries along with how to resolve
+/// their features. This is a list of all top-level packages that are intended
+/// to be part of the lock file (resolve output). These typically are a list
+/// of all workspace members.
+///
+/// * `replacements` - this is a list of `[replace]` directives found in the
+/// root of the workspace. The list here is a `PackageIdSpec` of what to
+/// replace and a `Dependency` to replace that with. In general it's not
+/// recommended to use `[replace]` any more and use `[patch]` instead, which
+/// is supported elsewhere.
+///
+/// * `registry` - this is the source from which all package summaries are
+/// loaded. It's expected that this is extensively configured ahead of time
+/// and is idempotent with our requests to it (aka returns the same results
+/// for the same query every time). Typically this is an instance of a
+/// `PackageRegistry`.
+///
+/// * `try_to_use` - this is a list of package ids which were previously found
+/// in the lock file. We heuristically prefer the ids listed in `try_to_use`
+/// when sorting candidates to activate, but otherwise this isn't used
+/// anywhere else.
+///
+/// * `config` - a location to print warnings and such, or `None` if no warnings
+/// should be printed
+///
+/// * `print_warnings` - whether or not to print backwards-compatibility
+/// warnings and such
pub fn resolve(
summaries: &[(Summary, Method)],
replacements: &[(PackageIdSpec, Dependency)],
registry: &mut Registry,
+ try_to_use: &[&PackageId],
config: Option<&Config>,
print_warnings: bool,
) -> CargoResult<Resolve> {
warnings: RcList::new(),
};
let _p = profile::start("resolving");
- let cx = activate_deps_loop(
- cx,
- &mut RegistryQueryer::new(registry, replacements),
- summaries,
- config,
- )?;
+ let mut registry = RegistryQueryer::new(registry, replacements, try_to_use);
+ let cx = activate_deps_loop(cx, &mut registry, summaries, config)?;
let mut resolve = Resolve {
graph: cx.graph(),
struct RegistryQueryer<'a> {
registry: &'a mut (Registry + 'a),
replacements: &'a [(PackageIdSpec, Dependency)],
+ try_to_use: HashSet<&'a PackageId>,
// TODO: with nll the Rc can be removed
cache: HashMap<Dependency, Rc<Vec<Candidate>>>,
}
impl<'a> RegistryQueryer<'a> {
- fn new(registry: &'a mut Registry, replacements: &'a [(PackageIdSpec, Dependency)]) -> Self {
+ fn new(
+ registry: &'a mut Registry,
+ replacements: &'a [(PackageIdSpec, Dependency)],
+ try_to_use: &'a [&'a PackageId],
+ ) -> Self {
RegistryQueryer {
registry,
replacements,
cache: HashMap::new(),
+ try_to_use: try_to_use.iter().cloned().collect(),
}
}
candidate.replace = replace;
}
- // When we attempt versions for a package, we'll want to start at
- // the maximum version and work our way down.
- ret.sort_unstable_by(|a, b| b.summary.version().cmp(a.summary.version()));
+ // When we attempt versions for a package we'll want to do so in a
+ // sorted fashion to pick the "best candidates" first. Currently we try
+ // prioritized summaries (those in `try_to_use`) and failing that we
+ // list everything from the maximum version to the lowest version.
+ ret.sort_unstable_by(|a, b| {
+ let a_in_previous = self.try_to_use.contains(a.summary.package_id());
+ let b_in_previous = self.try_to_use.contains(b.summary.package_id());
+ let a = (a_in_previous, a.summary.version());
+ let b = (b_in_previous, b.summary.version());
+ a.cmp(&b).reverse()
+ });
let out = Rc::new(ret);
use glob::glob;
use url::Url;
-use core::{Dependency, PackageIdSpec, Profile, Profiles};
+use core::registry::PackageRegistry;
use core::{EitherManifest, Package, SourceId, VirtualManifest};
+use core::{Dependency, PackageIdSpec, Profile, Profiles};
use ops;
+use sources::PathSource;
use util::errors::{CargoResult, CargoResultExt};
use util::paths;
use util::toml::read_manifest;
// cases `false` also results in the non-enforcement of dev-dependencies.
require_optional_deps: bool,
+ // A cache of lodaed packages for particular paths which is disjoint from
+ // `packages` up above, used in the `load` method down below.
loaded_packages: RefCell<HashMap<PathBuf, Package>>,
}
loaded.insert(manifest_path.to_path_buf(), package.clone());
Ok(package)
}
+
+ /// Preload the provided registry with already loaded packages.
+ ///
+ /// A workspace may load packages during construction/parsing/early phases
+ /// for various operations, and this preload step avoids doubly-loading and
+ /// parsing crates on the filesystem by inserting them all into the registry
+ /// with their in-memory formats.
+ pub fn preload(&self, registry: &mut PackageRegistry<'cfg>) {
+ // These can get weird as this generally represents a workspace during
+ // `cargo install`. Things like git repositories will actually have a
+ // `PathSource` with multiple entries in it, so the logic below is
+ // mostly just an optimization for normal `cargo build` in workspaces
+ // during development.
+ if self.is_ephemeral {
+ return;
+ }
+
+ for pkg in self.packages.packages.values() {
+ let pkg = match *pkg {
+ MaybePackage::Package(ref p) => p.clone(),
+ MaybePackage::Virtual(_) => continue,
+ };
+ let mut src = PathSource::new(
+ pkg.manifest_path(),
+ pkg.package_id().source_id(),
+ self.config,
+ );
+ src.preload_with(pkg);
+ registry.add_preloaded(Box::new(src));
+ }
+ }
}
impl<'cfg> Packages<'cfg> {
.pkg
.dependencies()
.iter()
- .filter(|d| d.matches_ignoring_source(dep.pkg.summary()))
+ .filter(|d| d.matches_ignoring_source(dep.pkg.package_id()))
.filter_map(|d| d.rename())
.next();
Ok((packages, resolved_with_overrides))
}
-fn resolve_with_registry(
- ws: &Workspace,
- registry: &mut PackageRegistry,
+fn resolve_with_registry<'cfg>(
+ ws: &Workspace<'cfg>,
+ registry: &mut PackageRegistry<'cfg>,
warn: bool,
) -> CargoResult<Resolve> {
let prev = ops::load_pkg_lockfile(ws)?;
///
/// The previous resolve normally comes from a lockfile. This function does not
/// read or write lockfiles from the filesystem.
-pub fn resolve_with_previous<'a>(
- registry: &mut PackageRegistry,
- ws: &Workspace,
+pub fn resolve_with_previous<'a, 'cfg>(
+ registry: &mut PackageRegistry<'cfg>,
+ ws: &Workspace<'cfg>,
method: Method,
previous: Option<&'a Resolve>,
to_avoid: Option<&HashSet<&'a PackageId>>,
// In the case where a previous instance of resolve is available, we
// want to lock as many packages as possible to the previous version
- // without disturbing the graph structure. To this end we perform
- // two actions here:
- //
- // 1. We inform the package registry of all locked packages. This
- // involves informing it of both the locked package's id as well
- // as the versions of all locked dependencies. The registry will
- // then takes this information into account when it is queried.
- //
- // 2. The specified package's summary will have its dependencies
- // modified to their precise variants. This will instruct the
- // first step of the resolution process to not query for ranges
- // but rather for precise dependency versions.
- //
- // This process must handle altered dependencies, however, as
- // it's possible for a manifest to change over time to have
- // dependencies added, removed, or modified to different version
- // ranges. To deal with this, we only actually lock a dependency
- // to the previously resolved version if the dependency listed
- // still matches the locked version.
+ // without disturbing the graph structure.
+ let mut try_to_use = Vec::new();
if let Some(r) = previous {
trace!("previous: {:?}", r);
- for node in r.iter().filter(keep) {
- let deps = r.deps_not_replaced(node).filter(keep).cloned().collect();
- registry.register_lock(node.clone(), deps);
- }
+ register_previous_locks(ws, registry, r, keep);
+
+ // Everything in the previous lock file we want to keep is prioritized
+ // in dependency selection if it comes up, aka we want to have
+ // conservative updates.
+ try_to_use.extend(r.iter().filter(keep).inspect(|id| {
+ debug!("attempting to prefer {}", id);
+ }));
}
if register_patches {
None => root_replace.to_vec(),
};
- let mut resolved = resolver::resolve(&summaries, &replace, registry, Some(ws.config()), warn)?;
+ ws.preload(registry);
+ let mut resolved = resolver::resolve(
+ &summaries,
+ &replace,
+ registry,
+ &try_to_use,
+ Some(ws.config()),
+ warn,
+ )?;
resolved.register_used_patches(registry.patches());
if let Some(previous) = previous {
resolved.merge_from(previous)?;
let ids: Vec<PackageId> = resolve.iter().cloned().collect();
registry.get(&ids)
}
+
+/// In this function we're responsible for informing the `registry` of all
+/// locked dependencies from the previous lock file we had, `resolve`.
+///
+/// This gets particularly tricky for a couple of reasons. The first is that we
+/// want all updates to be conservative, so we actually want to take the
+/// `resolve` into account (and avoid unnecessary registry updates and such).
+/// the second, however, is that we want to be resilient to updates of
+/// manifests. For example if a dependency is added or a version is changed we
+/// want to make sure that we properly re-resolve (conservatively) instead of
+/// providing an opaque error.
+///
+/// The logic here is somewhat subtle but there should be more comments below to
+/// help out, and otherwise feel free to ask on IRC if there's questions!
+///
+/// Note that this function, at the time of this writing, is basically the
+/// entire fix for #4127
+fn register_previous_locks<'a>(
+ ws: &Workspace,
+ registry: &mut PackageRegistry,
+ resolve: &'a Resolve,
+ keep: &Fn(&&'a PackageId) -> bool,
+) {
+ // Ok so we've been passed in a `keep` function which basically says "if I
+ // return true then this package wasn't listed for an update on the command
+ // line". AKA if we run `cargo update -p foo` then `keep(bar)` will return
+ // `true`, whereas `keep(foo)` will return `true` (roughly).
+ //
+ // This isn't actually quite what we want, however. Instead we want to
+ // further refine this `keep` function with *all transitive dependencies* of
+ // the packages we're not keeping. For example consider a case like this:
+ //
+ // * There's a crate `log`
+ // * There's a crate `serde` which depends on `log`
+ //
+ // Let's say we then run `cargo update -p serde`. This may *also* want to
+ // update the `log` dependency as our newer version of `serde` may have a
+ // new minimum version required for `log`. Now this isn't always guaranteed
+ // to work. What'll happen here is we *won't* lock the `log` dependency nor
+ // the `log` crate itself, but we will inform the registry "please prefer
+ // this version of `log`". That way if our newer version of serde works with
+ // the older version of `log`, we conservatively won't update `log`. If,
+ // however, nothing else in the dependency graph depends on `log` and the
+ // newer version of `serde` requires a new version of `log` it'll get pulled
+ // in (as we didn't accidentally lock it to an old version).
+ let mut avoid_locking = HashSet::new();
+ for node in resolve.iter() {
+ if !keep(&node) {
+ add_deps(resolve, node, &mut avoid_locking);
+ }
+ }
+
+ // Ok but the above loop isn't the entire story! Updates to the dependency
+ // graph can come from two locations, the `cargo update` command or
+ // manifests themselves. For example a manifest on the filesystem may
+ // have been updated to have an updated version requirement on `serde`. In
+ // this case both `keep(serde)` and `keep(log)` return `true` (the `keep`
+ // that's an argument to this function). We, however, don't want to keep
+ // either of those! Otherwise we'll get obscure resolve errors about locked
+ // versions.
+ //
+ // To solve this problem we iterate over all packages with path sources
+ // (aka ones with manifests that are changing) and take a look at all of
+ // their dependencies. If any dependency does not match something in the
+ // previous lock file, then we're guaranteed that the main resolver will
+ // update the source of this dependency no matter what. Knowing this we
+ // poison all packages from the same source, forcing them all to get
+ // updated.
+ //
+ // This may seem like a heavy hammer, and it is! It means that if you change
+ // anything from crates.io then all of crates.io becomes unlocked. Note,
+ // however, that we still want conservative updates. This currently happens
+ // because the first candidate the resolver picks is the previously locked
+ // version, and only if that fails to activate to we move on and try
+ // a different version. (giving the guise of conservative updates)
+ //
+ // For example let's say we had `serde = "0.1"` written in our lock file.
+ // When we later edit this to `serde = "0.1.3"` we don't want to lock serde
+ // at its old version, 0.1.1. Instead we want to allow it to update to
+ // `0.1.3` and update its own dependencies (like above). To do this *all
+ // crates from crates.io* are not locked (aka added to `avoid_locking`).
+ // For dependencies like `log` their previous version in the lock file will
+ // come up first before newer version, if newer version are available.
+ let mut path_deps = ws.members().cloned().collect::<Vec<_>>();
+ let mut visited = HashSet::new();
+ while let Some(member) = path_deps.pop() {
+ if !visited.insert(member.package_id().clone()) {
+ continue;
+ }
+ for dep in member.dependencies() {
+ let source = dep.source_id();
+
+ // If this is a path dependency then try to push it onto our
+ // worklist
+ if source.is_path() {
+ if let Ok(path) = source.url().to_file_path() {
+ if let Ok(pkg) = ws.load(&path.join("Cargo.toml")) {
+ path_deps.push(pkg);
+ }
+ continue;
+ }
+ }
+
+ // If we match *anything* in the dependency graph then we consider
+ // ourselves A-OK and assume that we'll resolve to that. If,
+ // however, nothing matches, then we poison the source of this
+ // dependencies and the previous lock file.
+ if resolve.iter().any(|id| dep.matches_ignoring_source(id)) {
+ continue;
+ }
+ for id in resolve.iter().filter(|id| id.source_id() == source) {
+ add_deps(resolve, id, &mut avoid_locking);
+ }
+ }
+ }
+
+ // Alright now that we've got our new, fresh, shiny, and refined `keep`
+ // function let's put it to action. Take a look at the previous lockfile,
+ // filter everything by this callback, and then shove everything else into
+ // the registry as a locked dependency.
+ let ref keep = |id: &&'a PackageId| keep(id) && !avoid_locking.contains(id);
+
+ for node in resolve.iter().filter(keep) {
+ let deps = resolve
+ .deps_not_replaced(node)
+ .filter(keep)
+ .cloned()
+ .collect();
+ registry.register_lock(node.clone(), deps);
+ }
+
+ /// recursively add `node` and all its transitive dependencies to `set`
+ fn add_deps<'a>(resolve: &'a Resolve, node: &'a PackageId, set: &mut HashSet<&'a PackageId>) {
+ if !set.insert(node) {
+ return;
+ }
+ debug!("ignoring any lock pointing directly at {}", node);
+ for dep in resolve.deps_not_replaced(node) {
+ add_deps(resolve, dep, set);
+ }
+ }
+}
}
}
+ pub fn preload_with(&mut self, pkg: Package) {
+ assert!(!self.updated);
+ assert!(!self.recursive);
+ assert!(self.packages.is_empty());
+ self.updated = true;
+ self.packages.push(pkg);
+ }
+
pub fn root_package(&mut self) -> CargoResult<Package> {
trace!("root_package; source={:?}", self);
buffer
}
+ pub fn uncomment_root_manifest(&self) {
+ let mut contents = String::new();
+ fs::File::open(self.root().join("Cargo.toml"))
+ .unwrap()
+ .read_to_string(&mut contents)
+ .unwrap();
+ fs::File::create(self.root().join("Cargo.toml"))
+ .unwrap()
+ .write_all(contents.replace("#", "").as_bytes())
+ .unwrap();
+ }
+
fn get_lib_prefix(kind: &str) -> &str {
match kind {
"lib" | "rlib" => "lib",
mod small_fd_limits;
mod test;
mod tool_paths;
+mod update;
mod verify_project;
mod version;
mod warn_on_failure;
p.cargo("update")
.arg("-p")
.arg(&format!("{}#bar", foo.url())),
- execs()
- .with_status(0)
- .with_stderr("[UPDATING] git repository `file://[..]`"),
+ execs().with_status(0).with_stderr(
+ "\
+[UPDATING] git repository `file://[..]`
+[UPDATING] registry `file://[..]`
+",
+ ),
);
assert_that(
p.cargo("update")
.arg("-p")
.arg("https://github.com/rust-lang/crates.io-index#bar"),
- execs()
- .with_status(0)
- .with_stderr("[UPDATING] registry `file://[..]`"),
+ execs().with_status(0).with_stderr(
+ "\
+[UPDATING] registry `file://[..]`
+",
+ ),
);
assert_that(p.cargo("build"), execs().with_status(0).with_stdout(""));
assert_that(
p.cargo("update").arg("-p").arg("hyper"),
- execs()
- .with_status(0)
- .with_stderr("[UPDATING] registry `[..]`"),
+ execs().with_status(0).with_stderr(
+ "\
+[UPDATING] registry `[..]`
+[UPDATING] hyper v0.6.5 -> v0.6.6
+[UPDATING] openssl v0.1.0 -> v0.1.1
+",
+ ),
);
}
let mut registry = MyRegistry(registry);
let summary = Summary::new(pkg.clone(), deps, BTreeMap::new(), None).unwrap();
let method = Method::Everything;
- let resolve = resolver::resolve(&[(summary, method)], &[], &mut registry, None, false)?;
+ let resolve = resolver::resolve(&[(summary, method)], &[], &mut registry, &[], None, false)?;
let res = resolve.iter().cloned().collect();
Ok(res)
}
--- /dev/null
+use std::fs::File;
+use std::io::prelude::*;
+
+use cargotest::support::{execs, project};
+use cargotest::support::registry::Package;
+use hamcrest::assert_that;
+
+#[test]
+fn minor_update_two_places() {
+ Package::new("log", "0.1.0").publish();
+ let p = project("foo")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ log = "0.1"
+ foo = { path = "foo" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ log = "0.1"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .build();
+
+ assert_that(p.cargo("build"), execs().with_status(0));
+ Package::new("log", "0.1.1").publish();
+
+ File::create(p.root().join("foo/Cargo.toml"))
+ .unwrap()
+ .write_all(
+ br#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ log = "0.1.1"
+ "#,
+ )
+ .unwrap();
+
+ assert_that(p.cargo("build"), execs().with_status(0));
+}
+
+#[test]
+fn transitive_minor_update() {
+ Package::new("log", "0.1.0").publish();
+ Package::new("serde", "0.1.0").dep("log", "0.1").publish();
+
+ let p = project("foo")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ serde = "0.1"
+ log = "0.1"
+ foo = { path = "foo" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ serde = "0.1"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .build();
+
+ assert_that(p.cargo("build"), execs().with_status(0));
+
+ Package::new("log", "0.1.1").publish();
+ Package::new("serde", "0.1.1").dep("log", "0.1.1").publish();
+
+ // Note that `serde` isn't actually updated here! The default behavior for
+ // `update` right now is to as conservatively as possible attempt to satisfy
+ // an update. In this case we previously locked the dependency graph to `log
+ // 0.1.0`, but nothing on the command line says we're allowed to update
+ // that. As a result the update of `serde` here shouldn't update to `serde
+ // 0.1.1` as that would also force an update to `log 0.1.1`.
+ //
+ // Also note that this is probably counterintuitive and weird. We may wish
+ // to change this one day.
+ assert_that(
+ p.cargo("update").arg("-p").arg("serde"),
+ execs().with_status(0).with_stderr(
+ "\
+[UPDATING] registry `[..]`
+",
+ ),
+ );
+}
+
+#[test]
+fn conservative() {
+ Package::new("log", "0.1.0").publish();
+ Package::new("serde", "0.1.0").dep("log", "0.1").publish();
+
+ let p = project("foo")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ serde = "0.1"
+ log = "0.1"
+ foo = { path = "foo" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ serde = "0.1"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .build();
+
+ assert_that(p.cargo("build"), execs().with_status(0));
+
+ Package::new("log", "0.1.1").publish();
+ Package::new("serde", "0.1.1").dep("log", "0.1").publish();
+
+ assert_that(
+ p.cargo("update").arg("-p").arg("serde"),
+ execs().with_status(0).with_stderr(
+ "\
+[UPDATING] registry `[..]`
+[UPDATING] serde v0.1.0 -> v0.1.1
+",
+ ),
+ );
+}
+
+#[test]
+fn update_via_new_dep() {
+ Package::new("log", "0.1.0").publish();
+ let p = project("foo")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ log = "0.1"
+ # foo = { path = "foo" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ log = "0.1.1"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .build();
+
+ assert_that(p.cargo("build"), execs().with_status(0));
+ Package::new("log", "0.1.1").publish();
+
+ p.uncomment_root_manifest();
+ assert_that(
+ p.cargo("build").env("RUST_LOG", "cargo=trace"),
+ execs().with_status(0),
+ );
+}
+
+#[test]
+fn update_via_new_member() {
+ Package::new("log", "0.1.0").publish();
+ let p = project("foo")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [workspace]
+ # members = [ "foo" ]
+
+ [dependencies]
+ log = "0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ log = "0.1.1"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .build();
+
+ assert_that(p.cargo("build"), execs().with_status(0));
+ Package::new("log", "0.1.1").publish();
+
+ p.uncomment_root_manifest();
+ assert_that(p.cargo("build"), execs().with_status(0));
+}
+
+#[test]
+fn add_dep_deep_new_requirement() {
+ Package::new("log", "0.1.0").publish();
+ let p = project("foo")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ log = "0.1"
+ # bar = "0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ assert_that(p.cargo("build"), execs().with_status(0));
+
+ Package::new("log", "0.1.1").publish();
+ Package::new("bar", "0.1.0").dep("log", "0.1.1").publish();
+
+ p.uncomment_root_manifest();
+ assert_that(p.cargo("build"), execs().with_status(0));
+}
+
+#[test]
+fn everything_real_deep() {
+ Package::new("log", "0.1.0").publish();
+ Package::new("foo", "0.1.0").dep("log", "0.1").publish();
+ let p = project("foo")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ foo = "0.1"
+ # bar = "0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ assert_that(p.cargo("build"), execs().with_status(0));
+
+ Package::new("log", "0.1.1").publish();
+ Package::new("bar", "0.1.0").dep("log", "0.1.1").publish();
+
+ p.uncomment_root_manifest();
+ assert_that(p.cargo("build"), execs().with_status(0));
+}