diff --git a/benches/benchsuite/benches/resolve.rs b/benches/benchsuite/benches/resolve.rs index e235441e1e2..f5e28322e24 100644 --- a/benches/benchsuite/benches/resolve.rs +++ b/benches/benchsuite/benches/resolve.rs @@ -31,6 +31,7 @@ fn do_resolve<'cfg>(config: &'cfg Config, ws_root: &Path) -> ResolveInfo<'cfg> { let specs = pkgs.to_package_id_specs(&ws).unwrap(); let has_dev_units = HasDevUnits::Yes; let force_all_targets = ForceAllTargets::No; + let max_rust_version = None; // Do an initial run to download anything necessary so that it does // not confuse criterion's warmup. let ws_resolve = cargo::ops::resolve_ws_with_opts( @@ -41,6 +42,7 @@ fn do_resolve<'cfg>(config: &'cfg Config, ws_root: &Path) -> ResolveInfo<'cfg> { &specs, has_dev_units, force_all_targets, + max_rust_version, ) .unwrap(); ResolveInfo { @@ -82,6 +84,7 @@ fn resolve_ws(c: &mut Criterion) { force_all_targets, .. } = lazy_info.get_or_insert_with(|| do_resolve(&config, &ws_root)); + let max_rust_version = None; b.iter(|| { cargo::ops::resolve_ws_with_opts( ws, @@ -91,6 +94,7 @@ fn resolve_ws(c: &mut Criterion) { specs, *has_dev_units, *force_all_targets, + max_rust_version, ) .unwrap(); }) diff --git a/crates/resolver-tests/src/lib.rs b/crates/resolver-tests/src/lib.rs index 12c474a0eba..cf56c376471 100644 --- a/crates/resolver-tests/src/lib.rs +++ b/crates/resolver-tests/src/lib.rs @@ -188,6 +188,7 @@ pub fn resolve_with_config_raw( .unwrap(); let opts = ResolveOpts::everything(); let start = Instant::now(); + let max_rust_version = None; let resolve = resolver::resolve( &[(summary, opts)], &[], @@ -195,6 +196,7 @@ pub fn resolve_with_config_raw( &VersionPreferences::default(), Some(config), true, + max_rust_version, ); // The largest test in our suite takes less then 30 sec. diff --git a/src/cargo/core/compiler/standard_lib.rs b/src/cargo/core/compiler/standard_lib.rs index b76c395b87e..5272c211bd7 100644 --- a/src/cargo/core/compiler/standard_lib.rs +++ b/src/cargo/core/compiler/standard_lib.rs @@ -145,6 +145,7 @@ pub fn resolve_std<'cfg>( let cli_features = CliFeatures::from_command_line( &features, /*all_features*/ false, /*uses_default_features*/ false, )?; + let max_rust_version = ws.rust_version(); let resolve = ops::resolve_ws_with_opts( &std_ws, target_data, @@ -153,6 +154,7 @@ pub fn resolve_std<'cfg>( &specs, HasDevUnits::No, crate::core::resolver::features::ForceAllTargets::No, + max_rust_version, )?; Ok(( resolve.pkg_set, diff --git a/src/cargo/core/resolver/dep_cache.rs b/src/cargo/core/resolver/dep_cache.rs index 9975330147b..e0a5ea7d763 100644 --- a/src/cargo/core/resolver/dep_cache.rs +++ b/src/cargo/core/resolver/dep_cache.rs @@ -21,6 +21,7 @@ use crate::core::{ }; use crate::util::errors::CargoResult; use crate::util::interning::InternedString; +use crate::util::PartialVersion; use anyhow::Context as _; use std::collections::{BTreeSet, HashMap, HashSet}; @@ -36,6 +37,7 @@ pub struct RegistryQueryer<'a> { /// versions first. That allows `cargo update -Z minimal-versions` which will /// specify minimum dependency versions to be used. minimal_versions: bool, + max_rust_version: Option, /// a cache of `Candidate`s that fulfil a `Dependency` (and whether `first_minimal_version`) registry_cache: HashMap<(Dependency, bool), Poll>>>, /// a cache of `Dependency`s that are required for a `Summary` @@ -57,12 +59,14 @@ impl<'a> RegistryQueryer<'a> { replacements: &'a [(PackageIdSpec, Dependency)], version_prefs: &'a VersionPreferences, minimal_versions: bool, + max_rust_version: Option, ) -> Self { RegistryQueryer { registry, replacements, version_prefs, minimal_versions, + max_rust_version, registry_cache: HashMap::new(), summary_cache: HashMap::new(), used_replacements: HashMap::new(), @@ -112,7 +116,9 @@ impl<'a> RegistryQueryer<'a> { let mut ret = Vec::new(); let ready = self.registry.query(dep, QueryKind::Exact, &mut |s| { - ret.push(s); + if self.max_rust_version.is_none() || s.rust_version() <= self.max_rust_version { + ret.push(s); + } })?; if ready.is_pending() { self.registry_cache diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs index e3da6fe5ad2..a7923427cb6 100644 --- a/src/cargo/core/resolver/mod.rs +++ b/src/cargo/core/resolver/mod.rs @@ -71,6 +71,7 @@ use crate::util::config::Config; use crate::util::errors::CargoResult; use crate::util::network::PollExt; use crate::util::profile; +use crate::util::PartialVersion; use self::context::Context; use self::dep_cache::RegistryQueryer; @@ -138,6 +139,7 @@ pub fn resolve( version_prefs: &VersionPreferences, config: Option<&Config>, check_public_visible_dependencies: bool, + mut max_rust_version: Option, ) -> CargoResult { let _p = profile::start("resolving"); let minimal_versions = match config { @@ -148,8 +150,19 @@ pub fn resolve( Some(config) => config.cli_unstable().direct_minimal_versions, None => false, }; - let mut registry = - RegistryQueryer::new(registry, replacements, version_prefs, minimal_versions); + if !config + .map(|c| c.cli_unstable().msrv_policy) + .unwrap_or(false) + { + max_rust_version = None; + } + let mut registry = RegistryQueryer::new( + registry, + replacements, + version_prefs, + minimal_versions, + max_rust_version, + ); let cx = loop { let cx = Context::new(check_public_visible_dependencies); let cx = activate_deps_loop( diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index 9ee0cbe0430..e21acec3d1c 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -23,6 +23,7 @@ use crate::util::edit_distance; use crate::util::errors::{CargoResult, ManifestError}; use crate::util::interning::InternedString; use crate::util::toml::{read_manifest, InheritableFields, TomlDependency, TomlProfiles}; +use crate::util::PartialVersion; use crate::util::{config::ConfigRelativePath, Config, Filesystem, IntoUrl}; use cargo_util::paths; use cargo_util::paths::normalize_path; @@ -595,6 +596,12 @@ impl<'cfg> Workspace<'cfg> { self } + /// Get the lowest-common denominator `package.rust-version` within the workspace, if specified + /// anywhere + pub fn rust_version(&self) -> Option { + self.members().filter_map(|pkg| pkg.rust_version()).min() + } + pub fn custom_metadata(&self) -> Option<&toml::Value> { self.custom_metadata.as_ref() } diff --git a/src/cargo/ops/cargo_compile/mod.rs b/src/cargo/ops/cargo_compile/mod.rs index 5469dd87b6e..9490246cdee 100644 --- a/src/cargo/ops/cargo_compile/mod.rs +++ b/src/cargo/ops/cargo_compile/mod.rs @@ -261,6 +261,7 @@ pub fn create_bcx<'a, 'cfg>( HasDevUnits::No } }; + let max_rust_version = ws.rust_version(); let resolve = ops::resolve_ws_with_opts( ws, &mut target_data, @@ -269,6 +270,7 @@ pub fn create_bcx<'a, 'cfg>( &specs, has_dev_units, crate::core::resolver::features::ForceAllTargets::No, + max_rust_version, )?; let WorkspaceResolve { mut pkg_set, diff --git a/src/cargo/ops/cargo_generate_lockfile.rs b/src/cargo/ops/cargo_generate_lockfile.rs index fddf83f1998..2bcdcbefd98 100644 --- a/src/cargo/ops/cargo_generate_lockfile.rs +++ b/src/cargo/ops/cargo_generate_lockfile.rs @@ -21,6 +21,7 @@ pub struct UpdateOptions<'a> { pub fn generate_lockfile(ws: &Workspace<'_>) -> CargoResult<()> { let mut registry = PackageRegistry::new(ws.config())?; + let max_rust_version = ws.rust_version(); let mut resolve = ops::resolve_with_previous( &mut registry, ws, @@ -30,6 +31,7 @@ pub fn generate_lockfile(ws: &Workspace<'_>) -> CargoResult<()> { None, &[], true, + max_rust_version, )?; ops::write_pkg_lockfile(ws, &mut resolve)?; Ok(()) @@ -48,6 +50,8 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes // that we're synchronized against other Cargos. let _lock = ws.config().acquire_package_cache_lock()?; + let max_rust_version = ws.rust_version(); + let previous_resolve = match ops::load_pkg_lockfile(ws)? { Some(resolve) => resolve, None => { @@ -67,6 +71,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes None, &[], true, + max_rust_version, )? } } @@ -125,6 +130,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes Some(&to_avoid), &[], true, + max_rust_version, )?; // Summarize what is changing for the user. diff --git a/src/cargo/ops/cargo_output_metadata.rs b/src/cargo/ops/cargo_output_metadata.rs index 5ab1a419528..c59e3bc3a01 100644 --- a/src/cargo/ops/cargo_output_metadata.rs +++ b/src/cargo/ops/cargo_output_metadata.rs @@ -135,6 +135,8 @@ fn build_resolve_graph( crate::core::resolver::features::ForceAllTargets::No }; + let max_rust_version = ws.rust_version(); + // Note that even with --filter-platform we end up downloading host dependencies as well, // as that is the behavior of download_accessible. let ws_resolve = ops::resolve_ws_with_opts( @@ -145,6 +147,7 @@ fn build_resolve_graph( &specs, HasDevUnits::Yes, force_all, + max_rust_version, )?; let package_map: BTreeMap = ws_resolve diff --git a/src/cargo/ops/cargo_package.rs b/src/cargo/ops/cargo_package.rs index 93469607bc8..2f9f770be51 100644 --- a/src/cargo/ops/cargo_package.rs +++ b/src/cargo/ops/cargo_package.rs @@ -423,6 +423,8 @@ fn build_lock(ws: &Workspace<'_>, orig_pkg: &Package) -> CargoResult { TomlManifest::to_real_manifest(&toml_manifest, false, source_id, package_root, config)?; let new_pkg = Package::new(manifest, orig_pkg.manifest_path()); + let max_rust_version = new_pkg.rust_version(); + // Regenerate Cargo.lock using the old one as a guide. let tmp_ws = Workspace::ephemeral(new_pkg, ws.config(), None, true)?; let mut tmp_reg = PackageRegistry::new(ws.config())?; @@ -435,6 +437,7 @@ fn build_lock(ws: &Workspace<'_>, orig_pkg: &Package) -> CargoResult { None, &[], true, + max_rust_version, )?; let pkg_set = ops::get_resolved_packages(&new_resolve, tmp_reg)?; diff --git a/src/cargo/ops/fix.rs b/src/cargo/ops/fix.rs index cd884ae7c12..2cae411a070 100644 --- a/src/cargo/ops/fix.rs +++ b/src/cargo/ops/fix.rs @@ -246,6 +246,8 @@ fn check_resolver_change(ws: &Workspace<'_>, opts: &FixOptions) -> CargoResult<( let mut target_data = RustcTargetData::new(ws, &opts.compile_opts.build_config.requested_kinds)?; let mut resolve_differences = |has_dev_units| -> CargoResult<(WorkspaceResolve<'_>, DiffMap)> { + let max_rust_version = ws.rust_version(); + let ws_resolve = ops::resolve_ws_with_opts( ws, &mut target_data, @@ -254,6 +256,7 @@ fn check_resolver_change(ws: &Workspace<'_>, opts: &FixOptions) -> CargoResult<( &specs, has_dev_units, crate::core::resolver::features::ForceAllTargets::No, + max_rust_version, )?; let feature_opts = FeatureOpts::new_behavior(ResolveBehavior::V2, has_dev_units); diff --git a/src/cargo/ops/resolve.rs b/src/cargo/ops/resolve.rs index f7251c46f8d..195a03616e3 100644 --- a/src/cargo/ops/resolve.rs +++ b/src/cargo/ops/resolve.rs @@ -69,6 +69,7 @@ use crate::core::{GitReference, PackageId, PackageIdSpec, PackageSet, SourceId, use crate::ops; use crate::sources::PathSource; use crate::util::errors::CargoResult; +use crate::util::PartialVersion; use crate::util::{profile, CanonicalUrl}; use anyhow::Context as _; use std::collections::{HashMap, HashSet}; @@ -106,8 +107,10 @@ version. This may also occur with an optional dependency that is not enabled."; /// This is a simple interface used by commands like `clean`, `fetch`, and /// `package`, which don't specify any options or features. pub fn resolve_ws<'a>(ws: &Workspace<'a>) -> CargoResult<(PackageSet<'a>, Resolve)> { + let max_rust_version = ws.rust_version(); + let mut registry = PackageRegistry::new(ws.config())?; - let resolve = resolve_with_registry(ws, &mut registry)?; + let resolve = resolve_with_registry(ws, &mut registry, max_rust_version)?; let packages = get_resolved_packages(&resolve, registry)?; Ok((packages, resolve)) } @@ -130,6 +133,7 @@ pub fn resolve_ws_with_opts<'cfg>( specs: &[PackageIdSpec], has_dev_units: HasDevUnits, force_all_targets: ForceAllTargets, + max_rust_version: Option, ) -> CargoResult> { let mut registry = PackageRegistry::new(ws.config())?; let mut add_patches = true; @@ -138,7 +142,7 @@ pub fn resolve_ws_with_opts<'cfg>( } else if ws.require_optional_deps() { // First, resolve the root_package's *listed* dependencies, as well as // downloading and updating all remotes and such. - let resolve = resolve_with_registry(ws, &mut registry)?; + let resolve = resolve_with_registry(ws, &mut registry, max_rust_version)?; // No need to add patches again, `resolve_with_registry` has done it. add_patches = false; @@ -184,6 +188,7 @@ pub fn resolve_ws_with_opts<'cfg>( None, specs, add_patches, + max_rust_version, )?; let pkg_set = get_resolved_packages(&resolved_with_overrides, registry)?; @@ -235,6 +240,7 @@ pub fn resolve_ws_with_opts<'cfg>( fn resolve_with_registry<'cfg>( ws: &Workspace<'cfg>, registry: &mut PackageRegistry<'cfg>, + max_rust_version: Option, ) -> CargoResult { let prev = ops::load_pkg_lockfile(ws)?; let mut resolve = resolve_with_previous( @@ -246,6 +252,7 @@ fn resolve_with_registry<'cfg>( None, &[], true, + max_rust_version, )?; if !ws.is_ephemeral() && ws.require_optional_deps() { @@ -278,6 +285,7 @@ pub fn resolve_with_previous<'cfg>( to_avoid: Option<&HashSet>, specs: &[PackageIdSpec], register_patches: bool, + max_rust_version: Option, ) -> CargoResult { // We only want one Cargo at a time resolving a crate graph since this can // involve a lot of frobbing of the global caches. @@ -505,6 +513,7 @@ pub fn resolve_with_previous<'cfg>( ws.unstable_features() .require(Feature::public_dependency()) .is_ok(), + max_rust_version, )?; let patches: Vec<_> = registry .patches() diff --git a/src/cargo/ops/tree/mod.rs b/src/cargo/ops/tree/mod.rs index 4a7048b628a..ce3bae8ccd0 100644 --- a/src/cargo/ops/tree/mod.rs +++ b/src/cargo/ops/tree/mod.rs @@ -150,6 +150,7 @@ pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<() } else { ForceAllTargets::No }; + let max_rust_version = ws.rust_version(); let ws_resolve = ops::resolve_ws_with_opts( ws, &mut target_data, @@ -158,6 +159,7 @@ pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<() &specs, has_dev, force_all, + max_rust_version, )?; let package_map: HashMap = ws_resolve diff --git a/src/cargo/util/semver_ext.rs b/src/cargo/util/semver_ext.rs index bf52f66a32b..9e0d3e50efe 100644 --- a/src/cargo/util/semver_ext.rs +++ b/src/cargo/util/semver_ext.rs @@ -108,7 +108,7 @@ impl From for OptVersionReq { } } -#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone, Debug)] pub struct PartialVersion { pub major: u64, pub minor: Option, diff --git a/tests/testsuite/cargo_add/rust_version_ignore/mod.rs b/tests/testsuite/cargo_add/rust_version_ignore/mod.rs index a382d95f154..f8aac0ad83f 100644 --- a/tests/testsuite/cargo_add/rust_version_ignore/mod.rs +++ b/tests/testsuite/cargo_add/rust_version_ignore/mod.rs @@ -26,7 +26,7 @@ fn case() { .current_dir(cwd) .masquerade_as_nightly_cargo(&["msrv-policy"]) .assert() - .success() + .code(101) .stdout_matches_path(curr_dir!().join("stdout.log")) .stderr_matches_path(curr_dir!().join("stderr.log")); diff --git a/tests/testsuite/cargo_add/rust_version_ignore/stderr.log b/tests/testsuite/cargo_add/rust_version_ignore/stderr.log index 430abe31b47..96bcbddc2a8 100644 --- a/tests/testsuite/cargo_add/rust_version_ignore/stderr.log +++ b/tests/testsuite/cargo_add/rust_version_ignore/stderr.log @@ -1,2 +1,7 @@ Updating `dummy-registry` index Adding rust-version-user v0.2.1 to dependencies. +error: failed to select a version for the requirement `rust-version-user = "^0.2.1"` +candidate versions found which didn't match: 0.2.1, 0.1.0 +location searched: `dummy-registry` index (which is replacing registry `crates-io`) +required by package `cargo-list-test-fixture v0.0.0 ([ROOT]/case)` +perhaps a crate was updated and forgotten to be re-vendored? diff --git a/tests/testsuite/rust_version.rs b/tests/testsuite/rust_version.rs index 1fce679ecb8..6bfaebda3d4 100644 --- a/tests/testsuite/rust_version.rs +++ b/tests/testsuite/rust_version.rs @@ -152,7 +152,7 @@ fn rust_version_too_high() { } #[cargo_test] -fn rust_version_dependency_fails() { +fn dependency_rust_version_newer_than_rustc() { Package::new("bar", "0.0.1") .rust_version("1.2345.0") .file("src/lib.rs", "fn other_stuff() {}") @@ -189,6 +189,189 @@ fn rust_version_dependency_fails() { p.cargo("check --ignore-rust-version").run(); } +#[cargo_test] +fn dependency_rust_version_newer_than_package() { + Package::new("bar", "1.6.0") + .rust_version("1.65.0") + .file("src/lib.rs", "fn other_stuff() {}") + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + rust-version = "1.60.0" + [dependencies] + bar = "1.0.0" + "#, + ) + .file("src/main.rs", "fn main(){}") + .build(); + + p.cargo("check --ignore-rust-version") + .arg("-Zmsrv-policy") + .masquerade_as_nightly_cargo(&["msrv-policy"]) + // This shouldn't fail + .with_status(101) + .with_stderr( + "\ +[UPDATING] `dummy-registry` index +[ERROR] failed to select a version for the requirement `bar = \"^1.0.0\"` +candidate versions found which didn't match: 1.6.0 +location searched: `dummy-registry` index (which is replacing registry `crates-io`) +required by package `foo v0.0.1 ([CWD])` +perhaps a crate was updated and forgotten to be re-vendored? +", + ) + .run(); + p.cargo("check") + .arg("-Zmsrv-policy") + .masquerade_as_nightly_cargo(&["msrv-policy"]) + .with_status(101) + // This should have a better error message + .with_stderr( + "\ +[UPDATING] `dummy-registry` index +[ERROR] failed to select a version for the requirement `bar = \"^1.0.0\"` +candidate versions found which didn't match: 1.6.0 +location searched: `dummy-registry` index (which is replacing registry `crates-io`) +required by package `foo v0.0.1 ([CWD])` +perhaps a crate was updated and forgotten to be re-vendored? +", + ) + .run(); +} + +#[cargo_test] +fn dependency_rust_version_older_and_newer_than_package() { + Package::new("bar", "1.5.0") + .rust_version("1.55.0") + .file("src/lib.rs", "fn other_stuff() {}") + .publish(); + Package::new("bar", "1.6.0") + .rust_version("1.65.0") + .file("src/lib.rs", "fn other_stuff() {}") + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + rust-version = "1.60.0" + [dependencies] + bar = "1.0.0" + "#, + ) + .file("src/main.rs", "fn main(){}") + .build(); + + p.cargo("check --ignore-rust-version") + .arg("-Zmsrv-policy") + .masquerade_as_nightly_cargo(&["msrv-policy"]) + // This should pick 1.6.0 + .with_stderr( + "\ +[UPDATING] `dummy-registry` index +[DOWNLOADING] crates ... +[DOWNLOADED] bar v1.5.0 (registry `dummy-registry`) +[CHECKING] bar v1.5.0 +[CHECKING] [..] +[FINISHED] [..] +", + ) + .run(); + p.cargo("check") + .arg("-Zmsrv-policy") + .masquerade_as_nightly_cargo(&["msrv-policy"]) + .with_stderr( + "\ +[FINISHED] [..] +", + ) + .run(); +} + +#[cargo_test] +fn workspace_with_mixed_rust_version() { + Package::new("bar", "1.4.0") + .rust_version("1.45.0") + .file("src/lib.rs", "fn other_stuff() {}") + .publish(); + Package::new("bar", "1.5.0") + .rust_version("1.55.0") + .file("src/lib.rs", "fn other_stuff() {}") + .publish(); + Package::new("bar", "1.6.0") + .rust_version("1.65.0") + .file("src/lib.rs", "fn other_stuff() {}") + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["lower"] + + [package] + name = "higher" + version = "0.0.1" + authors = [] + rust-version = "1.60.0" + [dependencies] + bar = "1.0.0" + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "lower/Cargo.toml", + r#" + [package] + name = "lower" + version = "0.0.1" + authors = [] + rust-version = "1.50.0" + [dependencies] + bar = "1.0.0" + "#, + ) + .file("lower/src/main.rs", "fn main() {}") + .build(); + + p.cargo("check --ignore-rust-version") + .arg("-Zmsrv-policy") + .masquerade_as_nightly_cargo(&["msrv-policy"]) + // This should pick 1.6.0 + .with_stderr( + "\ +[UPDATING] `dummy-registry` index +[DOWNLOADING] crates ... +[DOWNLOADED] bar v1.4.0 (registry `dummy-registry`) +[CHECKING] bar v1.4.0 +[CHECKING] [..] +[FINISHED] [..] +", + ) + .run(); + p.cargo("check") + .arg("-Zmsrv-policy") + .masquerade_as_nightly_cargo(&["msrv-policy"]) + .with_stderr( + "\ +[FINISHED] [..] +", + ) + .run(); +} + #[cargo_test] fn rust_version_older_than_edition() { project()