diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index 18eb96b5ff8..ab90cee0915 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -78,6 +78,7 @@ pub struct ManifestMetadata { pub repository: Option, // url pub documentation: Option, // url pub badges: BTreeMap>, + pub links: Option, } #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs index 633ae256033..2345a59da42 100644 --- a/src/cargo/core/resolver/mod.rs +++ b/src/cargo/core/resolver/mod.rs @@ -23,7 +23,9 @@ //! * Never try to activate a crate version which is incompatible. This means we //! only try crates which will actually satisfy a dependency and we won't ever //! try to activate a crate that's semver compatible with something else -//! activated (as we're only allowed to have one). +//! activated (as we're only allowed to have one) nor try to activate a crate +//! that has the same links attribute as something else +//! activated. //! * Always try to activate the highest version crate first. The default //! dependency in Cargo (e.g. when you write `foo = "0.1.2"`) is //! semver-compatible, so selecting the highest version possible will allow us @@ -325,11 +327,12 @@ enum GraphNode { // possible. #[derive(Clone)] struct Context<'a> { - // TODO: Both this and the map below are super expensive to clone. We should + // TODO: Both this and the two maps below are super expensive to clone. We should // switch to persistent hash maps if we can at some point or otherwise // make these much cheaper to clone in general. activations: Activations, resolve_features: HashMap>, + links: HashMap, // These are two cheaply-cloneable lists (O(1) clone) which are effectively // hash maps but are built up as "construction lists". We'll iterate these @@ -354,9 +357,10 @@ pub fn resolve(summaries: &[(Summary, Method)], let cx = Context { resolve_graph: RcList::new(), resolve_features: HashMap::new(), + links: HashMap::new(), resolve_replacements: RcList::new(), activations: HashMap::new(), - replacements: replacements, + replacements, warnings: RcList::new(), }; let _p = profile::start("resolving"); @@ -416,13 +420,13 @@ fn activate(cx: &mut Context, candidate.summary.package_id().clone())); } - let activated = cx.flag_activated(&candidate.summary, method); + let activated = cx.flag_activated(&candidate.summary, method)?; let candidate = match candidate.replace { Some(replace) => { cx.resolve_replacements.push((candidate.summary.package_id().clone(), replace.package_id().clone())); - if cx.flag_activated(&replace, method) && activated { + if cx.flag_activated(&replace, method)? && activated { return Ok(None); } trace!("activating {} (replacing {})", replace.package_id(), @@ -456,7 +460,7 @@ impl RcVecIter { fn new(vec: Rc>) -> RcVecIter { RcVecIter { rest: 0..vec.len(), - vec: vec, + vec, } } } @@ -528,6 +532,21 @@ impl Ord for DepsFrame { } } +#[derive(Clone, PartialOrd, Ord, PartialEq, Eq)] +enum ConflictReason { + Semver, + Links(String), +} + +impl ConflictReason { + fn is_links(&self) -> bool { + match self { + &ConflictReason::Semver => false, + &ConflictReason::Links(_) => true, + } + } +} + struct BacktrackFrame<'a> { cur: usize, context_backup: Context<'a>, @@ -542,26 +561,35 @@ struct BacktrackFrame<'a> { struct RemainingCandidates { remaining: RcVecIter, // note: change to RcList or something if clone is to expensive - conflicting_prev_active: HashSet, + conflicting_prev_active: HashMap, } impl RemainingCandidates { - fn next(&mut self, prev_active: &[Summary]) -> Result> { + fn next(&mut self, prev_active: &[Summary], links: &HashMap) -> Result> { // Filter the set of candidates based on the previously activated // versions for this dependency. We can actually use a version if it // precisely matches an activated version or if it is otherwise // incompatible with all other activated versions. Note that we // define "compatible" here in terms of the semver sense where if // the left-most nonzero digit is the same they're considered - // compatible. + // compatible unless we have a `*-sys` crate (defined by having a + // linked attribute) then we can only have one version. // // When we are done we return the set of previously activated // that conflicted with the ones we tried. If any of these change // then we would have considered different candidates. for (_, b) in self.remaining.by_ref() { + if let Some(link) = b.summary.links() { + if let Some(a) = links.get(link) { + if a != b.summary.package_id() { + self.conflicting_prev_active.insert(a.clone(), ConflictReason::Links(link.to_owned())); + continue + } + } + } if let Some(a) = prev_active.iter().find(|a| compatible(a.version(), b.summary.version())) { if *a != b.summary { - self.conflicting_prev_active.insert(a.package_id().clone()); + self.conflicting_prev_active.insert(a.package_id().clone(), ConflictReason::Semver); continue } } @@ -660,10 +688,10 @@ fn activate_deps_loop<'a>(mut cx: Context<'a>, dep.name(), prev_active.len()); let mut candidates = RemainingCandidates { remaining: RcVecIter::new(Rc::clone(&candidates)), - conflicting_prev_active: HashSet::new(), + conflicting_prev_active: HashMap::new(), }; - (candidates.next(prev_active), - candidates.clone().next(prev_active).is_ok(), + (candidates.next(prev_active, &cx.links), + candidates.clone().next(prev_active, &cx.links).is_ok(), candidates) }; @@ -673,7 +701,7 @@ fn activate_deps_loop<'a>(mut cx: Context<'a>, // 1. The version matches the dependency requirement listed for this // package // 2. There are no activated versions for this package which are - // semver-compatible, or there's an activated version which is + // semver/links-compatible, or there's an activated version which is // precisely equal to `candidate`. // // This means that we're going to attempt to activate each candidate in @@ -688,7 +716,7 @@ fn activate_deps_loop<'a>(mut cx: Context<'a>, cur, context_backup: Context::clone(&cx), deps_backup: >::clone(&remaining_deps), - remaining_candidates: remaining_candidates, + remaining_candidates, parent: Summary::clone(&parent), dep: Dependency::clone(&dep), features: Rc::clone(&features), @@ -756,21 +784,21 @@ fn find_candidate<'a>( cur: &mut usize, dep: &mut Dependency, features: &mut Rc>, - conflicting_activations: &mut HashSet, + conflicting_activations: &mut HashMap, ) -> Option { while let Some(mut frame) = backtrack_stack.pop() { let (next, has_another) = { let prev_active = frame.context_backup.prev_active(&frame.dep); ( - frame.remaining_candidates.next(prev_active), - frame.remaining_candidates.clone().next(prev_active).is_ok(), + frame.remaining_candidates.next(prev_active, &frame.context_backup.links), + frame.remaining_candidates.clone().next(prev_active, &frame.context_backup.links).is_ok(), ) }; if frame.context_backup.is_active(parent.package_id()) && conflicting_activations .iter() // note: a lot of redundant work in is_active for similar debs - .all(|con| frame.context_backup.is_active(con)) + .all(|(con, _)| frame.context_backup.is_active(con)) { continue; } @@ -800,7 +828,7 @@ fn activation_error(cx: &Context, registry: &mut Registry, parent: &Summary, dep: &Dependency, - conflicting_activations: HashSet, + conflicting_activations: HashMap, candidates: &[Candidate], config: Option<&Config>) -> CargoError { let graph = cx.graph(); @@ -816,26 +844,53 @@ fn activation_error(cx: &Context, dep_path_desc }; if !candidates.is_empty() { - let mut msg = format!("failed to select a version for `{}`.\n\ - all possible versions conflict with \ - previously selected packages.\n", - dep.name()); - msg.push_str("required by "); + let mut msg = format!("failed to select a version for `{}`.", dep.name()); + msg.push_str("\n ... required by "); msg.push_str(&describe_path(parent.package_id())); - let mut conflicting_activations: Vec<_> = conflicting_activations.iter().collect(); - conflicting_activations.sort_unstable(); - for v in conflicting_activations.iter().rev() { - msg.push_str("\n previously selected "); - msg.push_str(&describe_path(v)); - } - msg.push_str("\n possible versions to select: "); + msg.push_str("\nversions that meet the requirements `"); + msg.push_str(&dep.version_req().to_string()); + msg.push_str("` are: "); msg.push_str(&candidates.iter() .map(|v| v.summary.version()) .map(|v| v.to_string()) .collect::>() .join(", ")); + let mut conflicting_activations: Vec<_> = conflicting_activations.iter().collect(); + conflicting_activations.sort_unstable(); + let (links_errors, other_errors): (Vec<_>, Vec<_>) = conflicting_activations.drain(..).rev().partition(|&(_, r)| r.is_links()); + + for &(p, r) in &links_errors { + match r { + &ConflictReason::Links(ref link) => { + msg.push_str("\n\nthe package `"); + msg.push_str(dep.name()); + msg.push_str("` links to the native library `"); + msg.push_str(&link); + msg.push_str("`, but it conflicts with a previous package which links to `"); + msg.push_str(&link); + msg.push_str("` as well:\n"); + }, + _ => (), + } + msg.push_str(&describe_path(p)); + } + + if links_errors.is_empty() { + msg.push_str("\n\nall possible versions conflict with \ + previously selected packages."); + } + + for &(p, _) in &other_errors { + msg.push_str("\n\n previously selected "); + msg.push_str(&describe_path(p)); + } + + msg.push_str("\n\nfailed to select a version for `"); + msg.push_str(dep.name()); + msg.push_str("` which could resolve this conflict"); + return format_err!("{}", msg) } @@ -1046,12 +1101,12 @@ fn build_requirements<'a, 'b: 'a>(s: &'a Summary, method: &'b Method) } impl<'a> Context<'a> { - // Activate this summary by inserting it into our list of known activations. - // - // Returns if this summary with the given method is already activated. + /// Activate this summary by inserting it into our list of known activations. + /// + /// Returns true if this summary with the given method is already activated. fn flag_activated(&mut self, summary: &Summary, - method: &Method) -> bool { + method: &Method) -> CargoResult { let id = summary.package_id(); let prev = self.activations .entry(id.name().to_string()) @@ -1060,26 +1115,31 @@ impl<'a> Context<'a> { .or_insert(Vec::new()); if !prev.iter().any(|c| c == summary) { self.resolve_graph.push(GraphNode::Add(id.clone())); + if let Some(link) = summary.links() { + ensure!(self.links.insert(link.to_owned(), id.clone()).is_none(), + "Attempting to resolve a with more then one crate with the links={}. \n\ + This will not build as is. Consider rebuilding the .lock file.", link); + } prev.push(summary.clone()); - return false + return Ok(false) } debug!("checking if {} is already activated", summary.package_id()); let (features, use_default) = match *method { Method::Required { features, uses_default_features, .. } => { (features, uses_default_features) } - Method::Everything => return false, + Method::Everything => return Ok(false), }; let has_default_feature = summary.features().contains_key("default"); - match self.resolve_features.get(id) { + Ok(match self.resolve_features.get(id) { Some(prev) => { features.iter().all(|f| prev.contains(f)) && (!use_default || prev.contains("default") || !has_default_feature) } None => features.is_empty() && (!use_default || !has_default_feature) - } + }) } fn build_deps(&mut self, @@ -1191,7 +1251,7 @@ impl<'a> Context<'a> { .unwrap_or(&[]) } - fn is_active(&mut self, id: &PackageId) -> bool { + fn is_active(&self, id: &PackageId) -> bool { self.activations.get(id.name()) .and_then(|v| v.get(id.source_id())) .map(|v| v.iter().any(|s| s.package_id() == id)) diff --git a/src/cargo/core/summary.rs b/src/cargo/core/summary.rs index 8082c3c76a4..3613ccb7efc 100644 --- a/src/cargo/core/summary.rs +++ b/src/cargo/core/summary.rs @@ -22,12 +22,14 @@ struct Inner { dependencies: Vec, features: BTreeMap>, checksum: Option, + links: Option, } impl Summary { pub fn new(pkg_id: PackageId, dependencies: Vec, - features: BTreeMap>) -> CargoResult { + features: BTreeMap>, + links: Option) -> CargoResult { for dep in dependencies.iter() { if features.get(dep.name()).is_some() { bail!("Features and dependencies cannot have the \ @@ -66,9 +68,10 @@ impl Summary { Ok(Summary { inner: Rc::new(Inner { package_id: pkg_id, - dependencies: dependencies, - features: features, + dependencies, + features, checksum: None, + links, }), }) } @@ -82,6 +85,9 @@ impl Summary { pub fn checksum(&self) -> Option<&str> { self.inner.checksum.as_ref().map(|s| &s[..]) } + pub fn links(&self) -> Option<&str> { + self.inner.links.as_ref().map(|s| &s[..]) + } pub fn override_id(mut self, id: PackageId) -> Summary { Rc::make_mut(&mut self.inner).package_id = id; @@ -94,7 +100,7 @@ impl Summary { } pub fn map_dependencies(mut self, f: F) -> Summary - where F: FnMut(Dependency) -> Dependency { + where F: FnMut(Dependency) -> Dependency { { let slot = &mut Rc::make_mut(&mut self.inner).dependencies; let deps = mem::replace(slot, Vec::new()); diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index e4f32f6cdbf..daf80a6241e 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -159,7 +159,7 @@ fn transmit(config: &Config, let ManifestMetadata { ref authors, ref description, ref homepage, ref documentation, ref keywords, ref readme, ref repository, ref license, ref license_file, - ref categories, ref badges, + ref categories, ref badges, ref links, } = *manifest.metadata(); let readme_content = match *readme { Some(ref readme) => Some(paths::read(&pkg.root().join(readme))?), @@ -194,6 +194,7 @@ fn transmit(config: &Config, license: license.clone(), license_file: license_file.clone(), badges: badges.clone(), + links: links.clone(), }, tarball); match publish { diff --git a/src/cargo/sources/registry/index.rs b/src/cargo/sources/registry/index.rs index 23d3362c513..710c8ac8982 100644 --- a/src/cargo/sources/registry/index.rs +++ b/src/cargo/sources/registry/index.rs @@ -146,12 +146,12 @@ impl<'cfg> RegistryIndex<'cfg> { fn parse_registry_package(&mut self, line: &str) -> CargoResult<(Summary, bool)> { let RegistryPackage { - name, vers, cksum, deps, features, yanked + name, vers, cksum, deps, features, yanked, links } = super::DEFAULT_ID.set(&self.source_id, || { serde_json::from_str::(line) })?; let pkgid = PackageId::new(&name, &vers, &self.source_id)?; - let summary = Summary::new(pkgid, deps.inner, features)?; + let summary = Summary::new(pkgid, deps.inner, features, links)?; let summary = summary.set_checksum(cksum.clone()); if self.hashes.contains_key(&name[..]) { self.hashes.get_mut(&name[..]).unwrap().insert(vers, cksum); diff --git a/src/cargo/sources/registry/mod.rs b/src/cargo/sources/registry/mod.rs index 3ef6e67fd3c..e7edf91f169 100644 --- a/src/cargo/sources/registry/mod.rs +++ b/src/cargo/sources/registry/mod.rs @@ -220,6 +220,8 @@ struct RegistryPackage<'a> { features: BTreeMap>, cksum: String, yanked: Option, + #[serde(default)] + links: Option, } struct DependencyList { diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 633f2bf1ff8..7fc975a0a7d 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -674,7 +674,7 @@ impl TomlManifest { let include = project.include.clone().unwrap_or_default(); let summary = Summary::new(pkgid, deps, me.features.clone() - .unwrap_or_else(BTreeMap::new))?; + .unwrap_or_else(BTreeMap::new), project.links.clone())?; let metadata = ManifestMetadata { description: project.description.clone(), homepage: project.homepage.clone(), @@ -687,6 +687,7 @@ impl TomlManifest { keywords: project.keywords.clone().unwrap_or_default(), categories: project.categories.clone().unwrap_or_default(), badges: me.badges.clone().unwrap_or_default(), + links: project.links.clone(), }; let workspace_config = match (me.workspace.as_ref(), diff --git a/src/crates-io/lib.rs b/src/crates-io/lib.rs index be284984779..19d3e700c60 100644 --- a/src/crates-io/lib.rs +++ b/src/crates-io/lib.rs @@ -55,6 +55,8 @@ pub struct NewCrate { pub license_file: Option, pub repository: Option, pub badges: BTreeMap>, + #[serde(default)] + pub links: Option, } #[derive(Serialize)] diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index d148f5f5c0f..2c15367e56f 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -1021,13 +1021,17 @@ fn incompatible_dependencies() { execs().with_status(101) .with_stderr_contains("\ error: failed to select a version for `bad`. -all possible versions conflict with previously selected packages. -required by package `baz v0.1.0` + ... required by package `baz v0.1.0` ... which is depended on by `incompatible_dependencies v0.0.1 ([..])` +versions that meet the requirements `>= 1.0.1` are: 1.0.2, 1.0.1 + +all possible versions conflict with previously selected packages. + previously selected package `bad v1.0.0` ... which is depended on by `bar v0.1.0` ... which is depended on by `incompatible_dependencies v0.0.1 ([..])` - possible versions to select: 1.0.2, 1.0.1")); + +failed to select a version for `bad` which could resolve this conflict")); } #[test] @@ -1057,15 +1061,20 @@ fn incompatible_dependencies_with_multi_semver() { execs().with_status(101) .with_stderr_contains("\ error: failed to select a version for `bad`. + ... required by package `incompatible_dependencies v0.0.1 ([..])` +versions that meet the requirements `>= 1.0.1, <= 2.0.0` are: 2.0.0, 1.0.1 + all possible versions conflict with previously selected packages. -required by package `incompatible_dependencies v0.0.1 ([..])` + previously selected package `bad v2.0.1` ... which is depended on by `baz v0.1.0` ... which is depended on by `incompatible_dependencies v0.0.1 ([..])` + previously selected package `bad v1.0.0` ... which is depended on by `bar v0.1.0` ... which is depended on by `incompatible_dependencies v0.0.1 ([..])` - possible versions to select: 2.0.0, 1.0.1")); + +failed to select a version for `bad` which could resolve this conflict")); } #[test] diff --git a/tests/testsuite/build_script.rs b/tests/testsuite/build_script.rs index d88a0b5db56..b7fab0cd1b6 100644 --- a/tests/testsuite/build_script.rs +++ b/tests/testsuite/build_script.rs @@ -224,6 +224,7 @@ not have a custom build script #[test] fn links_duplicates() { + // this tests that the links_duplicates are caught at resolver time let p = project("foo") .file("Cargo.toml", r#" [project] @@ -253,20 +254,20 @@ fn links_duplicates() { assert_that(p.cargo("build"), execs().with_status(101) .with_stderr("\ -[ERROR] multiple packages link to native library `a`, but a native library can \ -be linked only once +error: failed to select a version for `a-sys`. + ... required by package `foo v0.5.0 ([..])` +versions that meet the requirements `*` are: 0.5.0 +the package `a-sys` links to the native library `a`, but it conflicts with a previous package which links to `a` as well: package `foo v0.5.0 ([..])` -links to native library `a` -package `a-sys v0.5.0 ([..])` - ... which is depended on by `foo v0.5.0 ([..])` -also links to native library `a` +failed to select a version for `a-sys` which could resolve this conflict ")); } #[test] fn links_duplicates_deep_dependency() { + // this tests that the links_duplicates are caught at resolver time let p = project("foo") .file("Cargo.toml", r#" [project] @@ -308,16 +309,15 @@ fn links_duplicates_deep_dependency() { assert_that(p.cargo("build"), execs().with_status(101) .with_stderr("\ -[ERROR] multiple packages link to native library `a`, but a native library can \ -be linked only once +error: failed to select a version for `a-sys`. + ... required by package `a v0.5.0 ([..])` + ... which is depended on by `foo v0.5.0 ([..])` +versions that meet the requirements `*` are: 0.5.0 +the package `a-sys` links to the native library `a`, but it conflicts with a previous package which links to `a` as well: package `foo v0.5.0 ([..])` -links to native library `a` -package `a-sys v0.5.0 ([..])` - ... which is depended on by `a v0.5.0 ([..])` - ... which is depended on by `foo v0.5.0 ([..])` -also links to native library `a` +failed to select a version for `a-sys` which could resolve this conflict ")); } @@ -2738,6 +2738,7 @@ fn deterministic_rustc_dependency_flags() { #[test] fn links_duplicates_with_cycle() { + // this tests that the links_duplicates are caught at resolver time let p = project("foo") .file("Cargo.toml", r#" [project] @@ -2780,17 +2781,14 @@ fn links_duplicates_with_cycle() { assert_that(p.cargo("build"), execs().with_status(101) .with_stderr("\ -[ERROR] multiple packages link to native library `a`, but a native library can \ -be linked only once +error: failed to select a version for `a`. + ... required by package `foo v0.5.0 ([..])` +versions that meet the requirements `*` are: 0.5.0 +the package `a` links to the native library `a`, but it conflicts with a previous package which links to `a` as well: package `foo v0.5.0 ([..])` - ... which is depended on by `b v0.5.0 ([..])` -links to native library `a` -package `a v0.5.0 (file://[..])` - ... which is depended on by `foo v0.5.0 ([..])` - ... which is depended on by `b v0.5.0 ([..])` -also links to native library `a` +failed to select a version for `a` which could resolve this conflict ")); } diff --git a/tests/testsuite/resolve.rs b/tests/testsuite/resolve.rs index a38decc6866..55d683feefa 100644 --- a/tests/testsuite/resolve.rs +++ b/tests/testsuite/resolve.rs @@ -29,7 +29,7 @@ fn resolve(pkg: &PackageId, deps: Vec, registry: &[Summary]) fn requires_precise(&self) -> bool { false } } let mut registry = MyRegistry(registry); - let summary = Summary::new(pkg.clone(), deps, BTreeMap::new()).unwrap(); + 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 res = resolve.iter().cloned().collect(); @@ -81,13 +81,17 @@ impl ToPkgId for (&'static str, String) { macro_rules! pkg { ($pkgid:expr => [$($deps:expr),+]) => ({ let d: Vec = vec![$($deps.to_dep()),+]; + let pkgid = $pkgid.to_pkgid(); + let link = if pkgid.name().ends_with("-sys") {Some(pkgid.name().to_string())} else {None}; - Summary::new($pkgid.to_pkgid(), d, BTreeMap::new()).unwrap() + Summary::new(pkgid, d, BTreeMap::new(), link).unwrap() }); - ($pkgid:expr) => ( - Summary::new($pkgid.to_pkgid(), Vec::new(), BTreeMap::new()).unwrap() - ) + ($pkgid:expr) => ({ + let pkgid = $pkgid.to_pkgid(); + let link = if pkgid.name().ends_with("-sys") {Some(pkgid.name().to_string())} else {None}; + Summary::new(pkgid, Vec::new(), BTreeMap::new(), link).unwrap() + }) } fn registry_loc() -> SourceId { @@ -96,7 +100,8 @@ fn registry_loc() -> SourceId { } fn pkg(name: &str) -> Summary { - Summary::new(pkg_id(name), Vec::new(), BTreeMap::new()).unwrap() + let link = if name.ends_with("-sys") {Some(name.to_string())} else {None}; + Summary::new(pkg_id(name), Vec::new(), BTreeMap::new(), link).unwrap() } fn pkg_id(name: &str) -> PackageId { @@ -112,7 +117,8 @@ fn pkg_id_loc(name: &str, loc: &str) -> PackageId { } fn pkg_loc(name: &str, loc: &str) -> Summary { - Summary::new(pkg_id_loc(name, loc), Vec::new(), BTreeMap::new()).unwrap() + let link = if name.ends_with("-sys") {Some(name.to_string())} else {None}; + Summary::new(pkg_id_loc(name, loc), Vec::new(), BTreeMap::new(), link).unwrap() } fn dep(name: &str) -> Dependency { dep_req(name, "1.0.0") } @@ -368,6 +374,33 @@ fn resolving_with_deep_backtracking() { ("baz", "1.0.1")]))); } +#[test] +fn resolving_with_sys_crates() { + // This is based on issues/4902 + // With `l` a normal library we get 2copies so everyone gets the newest compatible. + // But `l-sys` a library with a links attribute we make sure there is only one. + let reg = registry(vec![ + pkg!(("l-sys", "0.9.1")), + pkg!(("l-sys", "0.10.0")), + pkg!(("l", "0.9.1")), + pkg!(("l", "0.10.0")), + pkg!(("d", "1.0.0") => [dep_req("l-sys", ">=0.8.0, <=0.10.0"), dep_req("l", ">=0.8.0, <=0.10.0")]), + pkg!(("r", "1.0.0") => [dep_req("l-sys", "0.9"), dep_req("l", "0.9")]), + ]); + + let res = resolve(&pkg_id("root"), vec![ + dep_req("d", "1"), + dep_req("r", "1"), + ], ®).unwrap(); + + assert_that(&res, contains(names(&[("root", "1.0.0"), + ("d", "1.0.0"), + ("r", "1.0.0"), + ("l-sys", "0.9.1"), + ("l", "0.9.1"), + ("l", "0.10.0")]))); +} + #[test] fn resolving_with_constrained_sibling_backtrack_parent() { // There is no point in considering all of the backtrack_trap{1,2}