Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/cargo/core/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ pub struct CliUnstable {
pub offline: bool,
pub no_index_update: bool,
pub avoid_dev_deps: bool,
pub minimal_versions: bool,
}

impl CliUnstable {
Expand Down Expand Up @@ -317,6 +318,7 @@ impl CliUnstable {
"offline" => self.offline = true,
"no-index-update" => self.no_index_update = true,
"avoid-dev-deps" => self.avoid_dev_deps = true,
"minimal-versions" => self.minimal_versions = true,
_ => bail!("unknown `-Z` flag specified: {}", k),
}

Expand Down
29 changes: 25 additions & 4 deletions src/cargo/core/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,11 @@ pub fn resolve(
warnings: RcList::new(),
};
let _p = profile::start("resolving");
let mut registry = RegistryQueryer::new(registry, replacements, try_to_use);
let minimal_versions = match config {
Some(config) => config.cli_unstable().minimal_versions,
None => false,
};
let mut registry = RegistryQueryer::new(registry, replacements, try_to_use, minimal_versions);
let cx = activate_deps_loop(cx, &mut registry, summaries, config)?;

let mut resolve = Resolve {
Expand Down Expand Up @@ -683,19 +687,25 @@ struct RegistryQueryer<'a> {
try_to_use: &'a HashSet<&'a PackageId>,
// TODO: with nll the Rc can be removed
cache: HashMap<Dependency, Rc<Vec<Candidate>>>,
// If set the list of dependency candidates will be sorted by minimal
// versions first. That allows `cargo update -Z minimal-versions` which will
// specify minimum depedency versions to be used.
minimal_versions: bool,
}

impl<'a> RegistryQueryer<'a> {
fn new(
registry: &'a mut Registry,
replacements: &'a [(PackageIdSpec, Dependency)],
try_to_use: &'a HashSet<&'a PackageId>,
minimal_versions: bool,
) -> Self {
RegistryQueryer {
registry,
replacements,
cache: HashMap::new(),
try_to_use,
minimal_versions,
}
}

Expand Down Expand Up @@ -795,9 +805,20 @@ impl<'a> RegistryQueryer<'a> {
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 previous_cmp = a_in_previous.cmp(&b_in_previous).reverse();
match previous_cmp {
Ordering::Equal => {
let cmp = a.summary.version().cmp(&b.summary.version());
if self.minimal_versions == true {
// Lower version ordered first.
cmp
} else {
// Higher version ordered first.
cmp.reverse()
}
}
_ => previous_cmp,
}
});

let out = Rc::new(ret);
Expand Down
98 changes: 96 additions & 2 deletions tests/testsuite/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,26 @@ use hamcrest::{assert_that, contains, is_not};
use cargo::core::source::{GitReference, SourceId};
use cargo::core::dependency::Kind::{self, Development};
use cargo::core::{Dependency, PackageId, Registry, Summary};
use cargo::util::{CargoResult, ToUrl};
use cargo::util::{CargoResult, Config, ToUrl};
use cargo::core::resolver::{self, Method};

use cargotest::ChannelChanger;
use cargotest::support::{execs, project};
use cargotest::support::registry::Package;

fn resolve(
pkg: &PackageId,
deps: Vec<Dependency>,
registry: &[Summary],
) -> CargoResult<Vec<PackageId>> {
resolve_with_config(pkg, deps, registry, None)
}

fn resolve_with_config(
pkg: &PackageId,
deps: Vec<Dependency>,
registry: &[Summary],
config: Option<&Config>,
) -> CargoResult<Vec<PackageId>> {
struct MyRegistry<'a>(&'a [Summary]);
impl<'a> Registry for MyRegistry<'a> {
Expand All @@ -38,7 +51,7 @@ fn resolve(
&[],
&mut registry,
&HashSet::new(),
None,
config,
false,
)?;
let res = resolve.iter().cloned().collect();
Expand Down Expand Up @@ -320,6 +333,87 @@ fn test_resolving_maximum_version_with_transitive_deps() {
assert_that(&res, is_not(contains(names(&[("util", "1.1.1")]))));
}

#[test]
fn test_resolving_minimum_version_with_transitive_deps() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this test, it concisely demonstrates both properties of minimal-versions, that it chooses the smallest version while meeting all the requirements. Mabey a comment at the beginning explaining that for the next reader. @alexcrichton has been trying to teach me to leave more comments.

Also @alexcrichton, do you think this needs a full integration test that calls cargo -Z minimal-versions or is this sufficient?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry I missed this. Yeah @klausi mind adding a small integration test to ensure that the flag is plumbed to the right location?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

// When the minimal-versions config option is specified then the lowest
// possible version of a package should be selected. "util 1.0.0" can't be
// selected because of the requirements of "bar", so the minimum version
// must be 1.1.1.
let reg = registry(vec![
pkg!(("util", "1.2.2")),
pkg!(("util", "1.0.0")),
pkg!(("util", "1.1.1")),
pkg!("foo" => [dep_req("util", "1.0.0")]),
pkg!("bar" => [dep_req("util", ">=1.0.1")]),
]);

let mut config = Config::default().unwrap();
config
.configure(
1,
None,
&None,
false,
false,
&["minimal-versions".to_string()],
)
.unwrap();

let res = resolve_with_config(
&pkg_id("root"),
vec![dep_req("foo", "1.0.0"), dep_req("bar", "1.0.0")],
&reg,
Some(&config),
).unwrap();

assert_that(
&res,
contains(names(&[
("root", "1.0.0"),
("foo", "1.0.0"),
("bar", "1.0.0"),
("util", "1.1.1"),
])),
);
assert_that(&res, is_not(contains(names(&[("util", "1.2.2")]))));
assert_that(&res, is_not(contains(names(&[("util", "1.0.0")]))));
}

// Ensure that the "-Z minimal-versions" CLI option works and the minimal
// version of a dependency ends up in the lock file.
#[test]
fn minimal_version_cli() {
Package::new("dep", "1.0.0").publish();
Package::new("dep", "1.1.0").publish();

let p = project("foo")
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
authors = []
version = "0.0.1"

[dependencies]
dep = "1.0"
"#,
)
.file("src/main.rs", "fn main() {}")
.build();

assert_that(
p.cargo("generate-lockfile")
.masquerade_as_nightly_cargo()
.arg("-Zminimal-versions"),
execs().with_status(0),
);

let lock = p.read_lockfile();

assert!(lock.contains("dep 1.0.0"));
}

#[test]
fn resolving_incompat_versions() {
let reg = registry(vec![
Expand Down