Skip to content

Commit bcd0300

Browse files
committed
Auto merge of #5200 - klausi:minimal_versions, r=Eh2406
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.
2 parents 2f6cd6c + a38184f commit bcd0300

File tree

3 files changed

+123
-6
lines changed

3 files changed

+123
-6
lines changed

src/cargo/core/features.rs

+2
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ pub struct CliUnstable {
285285
pub offline: bool,
286286
pub no_index_update: bool,
287287
pub avoid_dev_deps: bool,
288+
pub minimal_versions: bool,
288289
}
289290

290291
impl CliUnstable {
@@ -317,6 +318,7 @@ impl CliUnstable {
317318
"offline" => self.offline = true,
318319
"no-index-update" => self.no_index_update = true,
319320
"avoid-dev-deps" => self.avoid_dev_deps = true,
321+
"minimal-versions" => self.minimal_versions = true,
320322
_ => bail!("unknown `-Z` flag specified: {}", k),
321323
}
322324

src/cargo/core/resolver/mod.rs

+25-4
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,11 @@ pub fn resolve(
430430
warnings: RcList::new(),
431431
};
432432
let _p = profile::start("resolving");
433-
let mut registry = RegistryQueryer::new(registry, replacements, try_to_use);
433+
let minimal_versions = match config {
434+
Some(config) => config.cli_unstable().minimal_versions,
435+
None => false,
436+
};
437+
let mut registry = RegistryQueryer::new(registry, replacements, try_to_use, minimal_versions);
434438
let cx = activate_deps_loop(cx, &mut registry, summaries, config)?;
435439

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

688696
impl<'a> RegistryQueryer<'a> {
689697
fn new(
690698
registry: &'a mut Registry,
691699
replacements: &'a [(PackageIdSpec, Dependency)],
692700
try_to_use: &'a HashSet<&'a PackageId>,
701+
minimal_versions: bool,
693702
) -> Self {
694703
RegistryQueryer {
695704
registry,
696705
replacements,
697706
cache: HashMap::new(),
698707
try_to_use,
708+
minimal_versions,
699709
}
700710
}
701711

@@ -795,9 +805,20 @@ impl<'a> RegistryQueryer<'a> {
795805
ret.sort_unstable_by(|a, b| {
796806
let a_in_previous = self.try_to_use.contains(a.summary.package_id());
797807
let b_in_previous = self.try_to_use.contains(b.summary.package_id());
798-
let a = (a_in_previous, a.summary.version());
799-
let b = (b_in_previous, b.summary.version());
800-
a.cmp(&b).reverse()
808+
let previous_cmp = a_in_previous.cmp(&b_in_previous).reverse();
809+
match previous_cmp {
810+
Ordering::Equal => {
811+
let cmp = a.summary.version().cmp(&b.summary.version());
812+
if self.minimal_versions == true {
813+
// Lower version ordered first.
814+
cmp
815+
} else {
816+
// Higher version ordered first.
817+
cmp.reverse()
818+
}
819+
}
820+
_ => previous_cmp,
821+
}
801822
});
802823

803824
let out = Rc::new(ret);

tests/testsuite/resolve.rs

+96-2
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,26 @@ use hamcrest::{assert_that, contains, is_not};
55
use cargo::core::source::{GitReference, SourceId};
66
use cargo::core::dependency::Kind::{self, Development};
77
use cargo::core::{Dependency, PackageId, Registry, Summary};
8-
use cargo::util::{CargoResult, ToUrl};
8+
use cargo::util::{CargoResult, Config, ToUrl};
99
use cargo::core::resolver::{self, Method};
1010

11+
use cargotest::ChannelChanger;
12+
use cargotest::support::{execs, project};
13+
use cargotest::support::registry::Package;
14+
1115
fn resolve(
1216
pkg: &PackageId,
1317
deps: Vec<Dependency>,
1418
registry: &[Summary],
19+
) -> CargoResult<Vec<PackageId>> {
20+
resolve_with_config(pkg, deps, registry, None)
21+
}
22+
23+
fn resolve_with_config(
24+
pkg: &PackageId,
25+
deps: Vec<Dependency>,
26+
registry: &[Summary],
27+
config: Option<&Config>,
1528
) -> CargoResult<Vec<PackageId>> {
1629
struct MyRegistry<'a>(&'a [Summary]);
1730
impl<'a> Registry for MyRegistry<'a> {
@@ -38,7 +51,7 @@ fn resolve(
3851
&[],
3952
&mut registry,
4053
&HashSet::new(),
41-
None,
54+
config,
4255
false,
4356
)?;
4457
let res = resolve.iter().cloned().collect();
@@ -327,6 +340,87 @@ fn test_resolving_maximum_version_with_transitive_deps() {
327340
assert_that(&res, is_not(contains(names(&[("util", "1.1.1")]))));
328341
}
329342

343+
#[test]
344+
fn test_resolving_minimum_version_with_transitive_deps() {
345+
// When the minimal-versions config option is specified then the lowest
346+
// possible version of a package should be selected. "util 1.0.0" can't be
347+
// selected because of the requirements of "bar", so the minimum version
348+
// must be 1.1.1.
349+
let reg = registry(vec![
350+
pkg!(("util", "1.2.2")),
351+
pkg!(("util", "1.0.0")),
352+
pkg!(("util", "1.1.1")),
353+
pkg!("foo" => [dep_req("util", "1.0.0")]),
354+
pkg!("bar" => [dep_req("util", ">=1.0.1")]),
355+
]);
356+
357+
let mut config = Config::default().unwrap();
358+
config
359+
.configure(
360+
1,
361+
None,
362+
&None,
363+
false,
364+
false,
365+
&["minimal-versions".to_string()],
366+
)
367+
.unwrap();
368+
369+
let res = resolve_with_config(
370+
&pkg_id("root"),
371+
vec![dep_req("foo", "1.0.0"), dep_req("bar", "1.0.0")],
372+
&reg,
373+
Some(&config),
374+
).unwrap();
375+
376+
assert_that(
377+
&res,
378+
contains(names(&[
379+
("root", "1.0.0"),
380+
("foo", "1.0.0"),
381+
("bar", "1.0.0"),
382+
("util", "1.1.1"),
383+
])),
384+
);
385+
assert_that(&res, is_not(contains(names(&[("util", "1.2.2")]))));
386+
assert_that(&res, is_not(contains(names(&[("util", "1.0.0")]))));
387+
}
388+
389+
// Ensure that the "-Z minimal-versions" CLI option works and the minimal
390+
// version of a dependency ends up in the lock file.
391+
#[test]
392+
fn minimal_version_cli() {
393+
Package::new("dep", "1.0.0").publish();
394+
Package::new("dep", "1.1.0").publish();
395+
396+
let p = project("foo")
397+
.file(
398+
"Cargo.toml",
399+
r#"
400+
[package]
401+
name = "foo"
402+
authors = []
403+
version = "0.0.1"
404+
405+
[dependencies]
406+
dep = "1.0"
407+
"#,
408+
)
409+
.file("src/main.rs", "fn main() {}")
410+
.build();
411+
412+
assert_that(
413+
p.cargo("generate-lockfile")
414+
.masquerade_as_nightly_cargo()
415+
.arg("-Zminimal-versions"),
416+
execs().with_status(0),
417+
);
418+
419+
let lock = p.read_lockfile();
420+
421+
assert!(lock.contains("dep 1.0.0"));
422+
}
423+
330424
#[test]
331425
fn resolving_incompat_versions() {
332426
let reg = registry(vec![

0 commit comments

Comments
 (0)