diff --git a/src/cargo/core/package.rs b/src/cargo/core/package.rs index 952b7cba26d..2709a480247 100644 --- a/src/cargo/core/package.rs +++ b/src/cargo/core/package.rs @@ -597,6 +597,97 @@ impl<'cfg> PackageSet<'cfg> { } } } + + Ok(()) + } + + /// Check if any dependency packages are defined in more than one registry + /// without an explicit registry defined in the dependency definition. If + /// there are, this function will warn the user that they may be at risk of + /// a dependency confusion attack. + pub(crate) fn warn_deps_defined_in_multiple_registries( + &self, + ws: &Workspace<'cfg>, + resolve: &Resolve, + root_ids: &[PackageId], + has_dev_units: HasDevUnits, + requested_kinds: &[CompileKind], + target_data: &RustcTargetData<'_>, + force_all_targets: ForceAllTargets, + ) -> CargoResult<()> { + // We need to build the possible sources that could cause confusion: + // we're only actually interested in registries here, since non-registry + // sources are more explicitly defined in Cargo.toml. + let mut check = match warn_multiple::DependencyConfusionChecker::new( + self.sources().sources().map(|(sid, _source)| sid), + ) { + Some(check) => check, + None => { + return Ok(()); + } + }; + + // We need to build a list of package+source combinations to check. This + // is essentially the Cartesian product of all package dependencies + // combined with all registry sources that are not the actual source + // that dependency comes from. + for (pid, deps) in root_ids.iter().flat_map(|root_id| { + PackageSet::filter_deps( + *root_id, + resolve, + has_dev_units, + requested_kinds, + target_data, + force_all_targets, + ) + }) { + check.check_package_deps(pid, deps); + } + + let multiply_defined = check.find_packages_defined_in_multiple_registries( + self.config.acquire_package_cache_lock()?, + self.sources_mut(), + )?; + + // Now we have a list of multiply defined packages, we can output that + // list, and suggest to the user how they can avoid the warning. + for (pid, others) in multiply_defined.into_iter() { + let mut other_sources = others + .into_iter() + .map(|sid| format!("`{}`", sid.display_registry_name())) + .collect::>(); + + if !other_sources.is_empty() { + // There's no technical reason to sort, but it keeps the test + // output stable. + other_sources.sort(); + + ws.config().shell().warn(&format!( + "package `{}` from {} is also defined in {} {}", + pid, + pid.source_id(), + if other_sources.len() == 1 { + "registry" + } else { + "registries" + }, + other_sources.join(", "), + ))?; + + ws.config().shell().note(&format!( + r#"you can specify the exact registry to use for the +`{}` dependency in Cargo.toml, eg: + +{} = {{ version = "{}", registry = "{}" }} +"#, + pid, + pid.name(), + pid.version(), + pid.source_id().display_registry_name(), + ))?; + } + } + Ok(()) } @@ -1178,3 +1269,148 @@ mod tls { }) } } + +mod warn_multiple { + use std::{ + cell::RefMut, + collections::{HashMap, HashSet}, + task::Poll, + }; + + use crate::{ + core::{Dependency, PackageId, SourceId, SourceMap}, + util::config::PackageCacheLock, + CargoResult, + }; + + /// Checks if packages are defined in more than one registry source. + pub(super) struct DependencyConfusionChecker { + registry_source_ids: Vec, + pending_checks: Vec<(PackageId, SourceId)>, + } + + impl DependencyConfusionChecker { + /// Instantiates a new checker based on the given sources. Only sources + /// that are actual registries will be used. + /// + /// If there are fewer than two registry sources, this function returns + /// `None`, and no further action is required to check for dependency + /// confusion. + pub(super) fn new<'a>(sources_iter: impl Iterator) -> Option { + let registry_source_ids = sources_iter + .filter_map(|sid| { + // We're only interested in registry sources, since other + // sources are always explicitly used in dependencies. + if sid.is_registry() { + Some(sid.clone()) + } else { + None + } + }) + .collect::>(); + + // If there are only zero or one registries, then there's nothing to + // check, since no dependency confusion can occur if there is no + // other possible source. + if registry_source_ids.len() > 1 { + Some(Self { + registry_source_ids, + pending_checks: Vec::new(), + }) + } else { + None + } + } + + /// Enqueues a package dependency to be checked for possible dependency + /// confusion, if required. + pub(super) fn check_package_deps(&mut self, pid: PackageId, deps: &HashSet) { + // If an explicit registry was given in a dependency, we don't want + // to warn, and no further work is required. + if deps.iter().any(|dep| dep.registry_id().is_some()) { + return; + } + + // Dependencies that are coming in from non-registry sources — such + // as Git repos, directories, and paths — should also be ignored, as + // the user will have specified the source in their Cargo.toml + // already. + let package_sid = pid.source_id(); + if !package_sid.is_registry() { + return; + } + + // Add a check for the package to the pending list for all registry + // sources that are not the actual source the package is coming + // from. + for sid in self.registry_source_ids.iter() { + if sid != &package_sid { + self.pending_checks.push((pid, *sid)); + } + } + } + + /// Returns packages that are available in multiple registry sources. + /// + /// Note that this requires package cache to be locked, as checking if a + /// source contains a particular package may trigger a pull from that + /// source. + pub(super) fn find_packages_defined_in_multiple_registries( + mut self, + _lock: PackageCacheLock<'_>, + mut sources: RefMut<'_, SourceMap<'_>>, + ) -> CargoResult>> { + // Basically, we want to iterate over the pending checks until we've + // ascertained what the result of each one is. The result may be a + // package+source combination that could cause dependency confusion, + // an error generated when checking if the source contains a + // package, or nothing if there's no confusion possible. + let mut results: Vec> = Vec::new(); + while !self.pending_checks.is_empty() { + self.pending_checks.retain(|(pid, sid)| { + if let Some(source) = sources.get_mut(*sid) { + match source.contains_package_name(&pid.name()) { + Poll::Ready(Ok(exists)) => { + if exists { + results.push(Ok((*pid, *sid))); + } + } + Poll::Ready(Err(e)) => { + results.push(Err(e)); + } + Poll::Pending => { + // This is the only scenario where we retain the + // pending check. + return true; + } + } + } else { + // This shouldn't happen in practice unless the source + // map was modified after `Self::new` was invoked, in + // which case there are probably bigger problems anyway. + results.push(Err(anyhow::format_err!( + "cannot find source from ID {:?}", + sid + ))); + } + false + }); + + for (_sid, source) in sources.sources_mut() { + source.block_until_ready()?; + } + } + + // Now we can turn the raw results into a neat HashMap keyed by each + // potentially affected package, which can then be used to report + // back to the user. + let mut by_package = HashMap::new(); + for result in results.into_iter() { + let (pid, sid) = result?; + by_package.entry(pid).or_insert_with(Vec::default).push(sid); + } + + Ok(by_package) + } + } +} diff --git a/src/cargo/core/source/mod.rs b/src/cargo/core/source/mod.rs index dca71b64e9d..6ef2c48cc39 100644 --- a/src/cargo/core/source/mod.rs +++ b/src/cargo/core/source/mod.rs @@ -94,6 +94,9 @@ pub trait Source { false } + /// Returns whether a specific package is defined within the source. + fn contains_package_name(&mut self, name: &str) -> Poll>; + /// Add a number of crates that should be whitelisted for showing up during /// queries, even if they are yanked. Currently only applies to registry /// sources. @@ -197,6 +200,11 @@ impl<'a, T: Source + ?Sized + 'a> Source for Box { (**self).is_replaced() } + /// Forwards to `Source::contains`. + fn contains_package_name(&mut self, name: &str) -> Poll> { + (**self).contains_package_name(name) + } + fn add_to_yanked_whitelist(&mut self, pkgs: &[PackageId]) { (**self).add_to_yanked_whitelist(pkgs); } @@ -268,6 +276,10 @@ impl<'a, T: Source + ?Sized + 'a> Source for &'a mut T { (**self).is_replaced() } + fn contains_package_name(&mut self, name: &str) -> Poll> { + (**self).contains_package_name(name) + } + fn add_to_yanked_whitelist(&mut self, pkgs: &[PackageId]) { (**self).add_to_yanked_whitelist(pkgs); } @@ -324,6 +336,11 @@ impl<'src> SourceMap<'src> { self.map.len() } + /// Like `HashMap::iter`. + pub fn sources<'a>(&'a self) -> impl Iterator { + self.map.iter().map(|(a, b)| (a, &**b)) + } + /// Like `HashMap::iter_mut`. pub fn sources_mut<'a>( &'a mut self, diff --git a/src/cargo/ops/resolve.rs b/src/cargo/ops/resolve.rs index ea5eded4aa2..3a34c7fc3ba 100644 --- a/src/cargo/ops/resolve.rs +++ b/src/cargo/ops/resolve.rs @@ -224,6 +224,16 @@ pub fn resolve_ws_with_opts<'cfg>( force_all_targets, )?; + pkg_set.warn_deps_defined_in_multiple_registries( + ws, + &resolved_with_overrides, + &member_ids, + has_dev_units, + requested_targets, + target_data, + force_all_targets, + )?; + Ok(WorkspaceResolve { pkg_set, workspace_resolve: resolve, diff --git a/src/cargo/sources/directory.rs b/src/cargo/sources/directory.rs index 46acb9f8630..a0d5f6fb893 100644 --- a/src/cargo/sources/directory.rs +++ b/src/cargo/sources/directory.rs @@ -156,6 +156,10 @@ impl<'cfg> Source for DirectorySource<'cfg> { Ok(()) } + fn contains_package_name(&mut self, name: &str) -> Poll> { + Poll::Ready(Ok(self.packages.keys().any(|id| id.name() == name))) + } + fn download(&mut self, id: PackageId) -> CargoResult { self.packages .get(&id) diff --git a/src/cargo/sources/git/source.rs b/src/cargo/sources/git/source.rs index 90c47093dce..b478faea229 100644 --- a/src/cargo/sources/git/source.rs +++ b/src/cargo/sources/git/source.rs @@ -230,6 +230,13 @@ impl<'cfg> Source for GitSource<'cfg> { format!("Git repository {}", self.source_id) } + fn contains_package_name(&mut self, name: &str) -> Poll> { + match &mut self.path_source { + Some(path_source) => path_source.contains_package_name(name), + None => Poll::Pending, + } + } + fn add_to_yanked_whitelist(&mut self, _pkgs: &[PackageId]) {} fn is_yanked(&mut self, _pkg: PackageId) -> Poll> { diff --git a/src/cargo/sources/path.rs b/src/cargo/sources/path.rs index 37e1e1f0f9d..f038885330d 100644 --- a/src/cargo/sources/path.rs +++ b/src/cargo/sources/path.rs @@ -561,6 +561,14 @@ impl<'cfg> Source for PathSource<'cfg> { } } + fn contains_package_name(&mut self, name: &str) -> Poll> { + self.update()?; + Poll::Ready(Ok(self + .packages + .iter() + .any(|package| package.name() == name))) + } + fn add_to_yanked_whitelist(&mut self, _pkgs: &[PackageId]) {} fn is_yanked(&mut self, _pkg: PackageId) -> Poll> { diff --git a/src/cargo/sources/registry/mod.rs b/src/cargo/sources/registry/mod.rs index 38fc1f94559..d692fedefe7 100644 --- a/src/cargo/sources/registry/mod.rs +++ b/src/cargo/sources/registry/mod.rs @@ -164,7 +164,7 @@ use std::collections::HashSet; use std::fs::{File, OpenOptions}; use std::io::{self, Write}; use std::path::{Path, PathBuf}; -use std::task::Poll; +use std::task::{ready, Poll}; use anyhow::Context as _; use cargo_util::paths::{self, exclude_from_backups_and_indexing}; @@ -879,6 +879,16 @@ impl<'cfg> Source for RegistrySource<'cfg> { self.source_id.display_index() } + fn contains_package_name(&mut self, name: &str) -> Poll> { + Poll::Ready(Ok(ready!(self.index.summaries( + name.into(), + &OptVersionReq::Any, + &mut *self.ops + ))? + .next() + .is_some())) + } + fn add_to_yanked_whitelist(&mut self, pkgs: &[PackageId]) { self.yanked_whitelist.extend(pkgs); } diff --git a/src/cargo/sources/replaced.rs b/src/cargo/sources/replaced.rs index 13191d2234f..cfd3945dda9 100644 --- a/src/cargo/sources/replaced.rs +++ b/src/cargo/sources/replaced.rs @@ -121,6 +121,10 @@ impl<'cfg> Source for ReplacedSource<'cfg> { true } + fn contains_package_name(&mut self, name: &str) -> Poll> { + self.inner.contains_package_name(name) + } + fn add_to_yanked_whitelist(&mut self, pkgs: &[PackageId]) { let pkgs = pkgs .iter() diff --git a/src/doc/src/reference/specifying-dependencies.md b/src/doc/src/reference/specifying-dependencies.md index 8d9eac308b8..c6862693939 100644 --- a/src/doc/src/reference/specifying-dependencies.md +++ b/src/doc/src/reference/specifying-dependencies.md @@ -119,6 +119,14 @@ to the name of the registry to use. some-crate = { version = "1.0", registry = "my-registry" } ``` +To explicitly depend on a package on [crates.io], the `registry` key can be set +to `crates-io`. + +```toml +[dependencies] +some-crate = { version = "1.0", registry = "crates-io" } +``` + > **Note**: [crates.io] does not allow packages to be published with > dependencies on other registries. diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index a1e293acd81..58c0a02a0c9 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -134,6 +134,7 @@ mod update; mod vendor; mod verify_project; mod version; +mod warn_multiple_registries; mod warn_on_failure; mod weak_dep_features; mod workspaces; diff --git a/tests/testsuite/patch.rs b/tests/testsuite/patch.rs index 681c02416bb..67a6e7b683c 100644 --- a/tests/testsuite/patch.rs +++ b/tests/testsuite/patch.rs @@ -2412,6 +2412,12 @@ fn can_update_with_alt_reg() { [UPDATING] `dummy-registry` index [DOWNLOADING] crates ... [DOWNLOADED] bar v0.1.1 (registry `alternative`) +[WARNING] package `bar v0.1.1 (registry `alternative`)` from registry `alternative` is also defined in registry `crates-io` +[NOTE] you can specify the exact registry to use for the +`bar v0.1.1 (registry `alternative`)` dependency in Cargo.toml, eg: + +bar = { version = \"0.1.1\", registry = \"alternative\" } + [CHECKING] bar v0.1.1 (registry `alternative`) [CHECKING] foo v0.1.0 ([..]/foo) [FINISHED] [..] @@ -2457,6 +2463,12 @@ fn can_update_with_alt_reg() { [UPDATING] `dummy-registry` index [DOWNLOADING] crates ... [DOWNLOADED] bar v0.1.2 (registry `alternative`) +[WARNING] package `bar v0.1.2 (registry `alternative`)` from registry `alternative` is also defined in registry `crates-io` +[NOTE] you can specify the exact registry to use for the +`bar v0.1.2 (registry `alternative`)` dependency in Cargo.toml, eg: + +bar = { version = \"0.1.2\", registry = \"alternative\" } + [CHECKING] bar v0.1.2 (registry `alternative`) [CHECKING] foo v0.1.0 ([..]/foo) [FINISHED] [..] diff --git a/tests/testsuite/warn_multiple_registries.rs b/tests/testsuite/warn_multiple_registries.rs new file mode 100644 index 00000000000..532e4b2dfdd --- /dev/null +++ b/tests/testsuite/warn_multiple_registries.rs @@ -0,0 +1,424 @@ +//! Tests for the warnings issued when packages are available in multiple +//! registries without being disambiguated. + +use cargo_test_support::{ + basic_manifest, git, project, + registry::{self, Package}, +}; + +#[cargo_test] +fn same_version_in_two_registries() { + // The basic test: what happens if a package is available in two registries? + // Note that both registries have to actually be used in the dependencies + // for the warning to fire. + + registry::alt_init(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + bar = "0.1.2" + in-alternative = { version = "0.1.0", registry = "alternative" } + in-default = "0.1.1" + "#, + ) + .file("src/lib.rs", "") + .build(); + + Package::new("in-alternative", "0.1.0") + .alternative(true) + .publish(); + Package::new("in-default", "0.1.1").publish(); + Package::new("bar", "0.1.2").alternative(true).publish(); + Package::new("bar", "0.1.2").publish(); + + // Note one small piece of weirdness here: if a registry isn't defined in + // the dependency, the default is `crates-io` even if `crates-io` has been + // replaced by another source. + p.cargo("check") + .with_stderr_unordered( + "\ +[UPDATING] `dummy-registry` index +[UPDATING] `alternative` index +[DOWNLOADING] crates ... +[DOWNLOADED] in-default v0.1.1 (registry `dummy-registry`) +[DOWNLOADED] in-alternative v0.1.0 (registry `alternative`) +[DOWNLOADED] bar v0.1.2 (registry `dummy-registry`) +[WARNING] package `bar v0.1.2` from registry `crates-io` is also defined in registry `alternative` +[NOTE] you can specify the exact registry to use for the +`bar v0.1.2` dependency in Cargo.toml, eg: + +bar = { version = \"0.1.2\", registry = \"crates-io\" } + +[CHECKING] bar v0.1.2 +[CHECKING] in-alternative v0.1.0 (registry `alternative`) +[CHECKING] in-default v0.1.1 +[CHECKING] foo v0.0.1 ([..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s +", + ) + .run(); +} + +#[cargo_test] +fn different_versions_in_two_registries() { + // Checks should take place on package names only, not versions. + + registry::alt_init(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + bar = "0.1.2" + in-alternative = { version = "0.1.0", registry = "alternative" } + in-default = "0.1.1" + "#, + ) + .file("src/lib.rs", "") + .build(); + + Package::new("in-alternative", "0.1.0") + .alternative(true) + .publish(); + Package::new("in-default", "0.1.1").publish(); + Package::new("bar", "1.2.3").alternative(true).publish(); + Package::new("bar", "0.1.2").publish(); + + p.cargo("check") + .with_stderr_unordered( + "\ +[UPDATING] `dummy-registry` index +[UPDATING] `alternative` index +[DOWNLOADING] crates ... +[DOWNLOADED] in-default v0.1.1 (registry `dummy-registry`) +[DOWNLOADED] in-alternative v0.1.0 (registry `alternative`) +[DOWNLOADED] bar v0.1.2 (registry `dummy-registry`) +[WARNING] package `bar v0.1.2` from registry `crates-io` is also defined in registry `alternative` +[NOTE] you can specify the exact registry to use for the +`bar v0.1.2` dependency in Cargo.toml, eg: + +bar = { version = \"0.1.2\", registry = \"crates-io\" } + +[CHECKING] bar v0.1.2 +[CHECKING] in-alternative v0.1.0 (registry `alternative`) +[CHECKING] in-default v0.1.1 +[CHECKING] foo v0.0.1 ([..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s +", + ) + .run(); +} + +#[cargo_test] +fn explicit_dep_registry() { + // Dependencies with explicit registries should not generate warnings. + + registry::alt_init(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + bar = { version = "0.1.2", registry = "alternative" } + in-alternative = { version = "0.1.0", registry = "alternative" } + in-default = "0.1.1" + "#, + ) + .file("src/lib.rs", "") + .build(); + + Package::new("in-alternative", "0.1.0") + .alternative(true) + .publish(); + Package::new("in-default", "0.1.1").publish(); + Package::new("bar", "0.1.2").alternative(true).publish(); + Package::new("bar", "0.1.2").publish(); + + p.cargo("check") + .with_stderr_unordered( + "\ +[UPDATING] `dummy-registry` index +[UPDATING] `alternative` index +[DOWNLOADING] crates ... +[DOWNLOADED] in-default v0.1.1 (registry `dummy-registry`) +[DOWNLOADED] in-alternative v0.1.0 (registry `alternative`) +[DOWNLOADED] bar v0.1.2 (registry `alternative`) +[CHECKING] bar v0.1.2 (registry `alternative`) +[CHECKING] in-alternative v0.1.0 (registry `alternative`) +[CHECKING] in-default v0.1.1 +[CHECKING] foo v0.0.1 ([..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s +", + ) + .run(); +} + +#[cargo_test] +fn path_dep() { + // Dependencies defined as path deps should never warn. + + registry::alt_init(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + bar = { path = "bar" } + in-alternative = { version = "0.1.0", registry = "alternative" } + in-default = "0.1.1" + "#, + ) + .file("src/lib.rs", "") + .file("bar/Cargo.toml", &basic_manifest("bar", "1.1.2")) + .file("bar/src/lib.rs", "") + .build(); + + Package::new("in-alternative", "0.1.0") + .alternative(true) + .publish(); + Package::new("in-default", "0.1.1").publish(); + Package::new("bar", "0.1.2").alternative(true).publish(); + Package::new("bar", "0.1.2").publish(); + + p.cargo("check") + .with_stderr_unordered( + "\ +[UPDATING] `dummy-registry` index +[UPDATING] `alternative` index +[DOWNLOADING] crates ... +[DOWNLOADED] in-default v0.1.1 (registry `dummy-registry`) +[DOWNLOADED] in-alternative v0.1.0 (registry `alternative`) +[CHECKING] bar v1.1.2 ([CWD]/bar) +[CHECKING] in-alternative v0.1.0 (registry `alternative`) +[CHECKING] in-default v0.1.1 +[CHECKING] foo v0.0.1 ([..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s +", + ) + .run(); +} + +#[cargo_test] +fn git_dep() { + // Dependencies defined as Git deps should never warn. + + let (dep_project, _dep_repo) = git::new_repo("bar", |p| { + p.file("Cargo.toml", &basic_manifest("bar", "1.1.2")) + .file("src/lib.rs", "") + }); + + registry::alt_init(); + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + bar = {{ git = "{}" }} + in-alternative = {{ version = "0.1.0", registry = "alternative" }} + in-default = "0.1.1" + "#, + dep_project.url() + ), + ) + .file("src/lib.rs", "") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.2")) + .file("bar/src/lib.rs", "") + .build(); + + Package::new("in-alternative", "0.1.0") + .alternative(true) + .publish(); + Package::new("in-default", "0.1.1").publish(); + Package::new("bar", "0.1.2").alternative(true).publish(); + Package::new("bar", "0.1.2").publish(); + + p.cargo("check") + .with_stderr_unordered( + "\ +[UPDATING] `dummy-registry` index +[UPDATING] `alternative` index +[DOWNLOADING] crates ... +[DOWNLOADED] in-default v0.1.1 (registry `dummy-registry`) +[DOWNLOADED] in-alternative v0.1.0 (registry `alternative`) +[UPDATING] git repository `[..]/bar` +[CHECKING] bar v1.1.2 ([..]/bar#[..]) +[CHECKING] in-alternative v0.1.0 (registry `alternative`) +[CHECKING] in-default v0.1.1 +[CHECKING] foo v0.0.1 ([..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s +", + ) + .run(); +} + +#[cargo_test] +fn other_dep_types() { + // Ensure we generate warnings on build and dev dependencies as well. + + registry::alt_init(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + main = "0.1.2" + in-alternative = { version = "0.1.0", registry = "alternative" } + in-default = "0.1.1" + + [build-dependencies] + build = "0.1.3" + + [dev-dependencies] + dev = "0.1.4" + "#, + ) + .file("src/lib.rs", "") + .build(); + + Package::new("in-alternative", "0.1.0") + .alternative(true) + .publish(); + Package::new("in-default", "0.1.1").publish(); + + for (package, version) in [("main", "0.1.2"), ("build", "0.1.3"), ("dev", "0.1.4")] { + Package::new(package, version).alternative(true).publish(); + Package::new(package, version).publish(); + } + + // Note one small piece of weirdness here: if a registry isn't defined in + // the dependency, the default is `crates-io` even if `crates-io` has been + // replaced by another source. + p.cargo("check --tests") + .with_stderr_unordered( + "\ +[UPDATING] `dummy-registry` index +[UPDATING] `alternative` index +[DOWNLOADING] crates ... +[DOWNLOADED] in-default v0.1.1 (registry `dummy-registry`) +[DOWNLOADED] in-alternative v0.1.0 (registry `alternative`) +[DOWNLOADED] main v0.1.2 (registry `dummy-registry`) +[DOWNLOADED] build v0.1.3 (registry `dummy-registry`) +[DOWNLOADED] dev v0.1.4 (registry `dummy-registry`) +[WARNING] package `main v0.1.2` from registry `crates-io` is also defined in registry `alternative` +[NOTE] you can specify the exact registry to use for the +`main v0.1.2` dependency in Cargo.toml, eg: + +main = { version = \"0.1.2\", registry = \"crates-io\" } + +[WARNING] package `build v0.1.3` from registry `crates-io` is also defined in registry `alternative` +[NOTE] you can specify the exact registry to use for the +`build v0.1.3` dependency in Cargo.toml, eg: + +build = { version = \"0.1.3\", registry = \"crates-io\" } + +[WARNING] package `dev v0.1.4` from registry `crates-io` is also defined in registry `alternative` +[NOTE] you can specify the exact registry to use for the +`dev v0.1.4` dependency in Cargo.toml, eg: + +dev = { version = \"0.1.4\", registry = \"crates-io\" } + +[CHECKING] main v0.1.2 +[CHECKING] dev v0.1.4 +[CHECKING] in-alternative v0.1.0 (registry `alternative`) +[CHECKING] in-default v0.1.1 +[CHECKING] foo v0.0.1 ([..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s +", + ) + .run(); +} + +#[cargo_test] +fn other_dep_types_explicit_registries() { + // Ensure we don't generate warnings on build and dev dependencies when they + // have explicit registries defined. + + registry::alt_init(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + main = { version = "0.1.2", registry = "alternative" } + in-alternative = { version = "0.1.0", registry = "alternative" } + in-default = "0.1.1" + + [build-dependencies] + build = { version = "0.1.3", registry = "alternative" } + + [dev-dependencies] + dev = { version = "0.1.4", registry = "crates-io" } + "#, + ) + .file("src/lib.rs", "") + .build(); + + Package::new("in-alternative", "0.1.0") + .alternative(true) + .publish(); + Package::new("in-default", "0.1.1").publish(); + + for (package, version) in [("main", "0.1.2"), ("build", "0.1.3"), ("dev", "0.1.4")] { + Package::new(package, version).alternative(true).publish(); + Package::new(package, version).publish(); + } + + p.cargo("check --tests") + .with_stderr_unordered( + "\ +[UPDATING] `dummy-registry` index +[UPDATING] `alternative` index +[DOWNLOADING] crates ... +[DOWNLOADED] in-default v0.1.1 (registry `dummy-registry`) +[DOWNLOADED] in-alternative v0.1.0 (registry `alternative`) +[DOWNLOADED] main v0.1.2 (registry `alternative`) +[DOWNLOADED] build v0.1.3 (registry `alternative`) +[DOWNLOADED] dev v0.1.4 (registry `dummy-registry`) +[CHECKING] main v0.1.2 (registry `alternative`) +[CHECKING] dev v0.1.4 +[CHECKING] in-alternative v0.1.0 (registry `alternative`) +[CHECKING] in-default v0.1.1 +[CHECKING] foo v0.0.1 ([..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s +", + ) + .run(); +}