Skip to content

Commit

Permalink
Auto merge of #5200 - klausi:minimal_versions, r=Eh2406
Browse files Browse the repository at this point in the history
feat(resolver): Add CLI option to resolve minimal version dependencies

Fixes #4100

Test cases are still missing. We need to come up with a plan what cases we want to cover.

Thanks a lot to @Eh2406 for very helpful instructions to kick this off.
  • Loading branch information
bors committed Mar 23, 2018
2 parents 2f6cd6c + a38184f commit bcd0300
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 6 deletions.
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 @@ -327,6 +340,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() {
// 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

0 comments on commit bcd0300

Please sign in to comment.