Skip to content

Commit b3df526

Browse files
committed
Auto merge of #5180 - alexcrichton:transitive-update, r=matklad
Don't abort resolution on transitive updates This commit is directed at fixing #4127, allowing the resolver to automatically perform transitive updates when required. A few use casese and tagged links are hanging off #4127 itself, but the crux of the issue happens when you either add a dependency or update a version requirement in `Cargo.toml` which conflicts with something listed in your `Cargo.lock`. In this case Cargo would previously provide an obscure "cannot resolve" error whereas this commit updates Cargo to automatically perform a conservative re-resolution of the dependency graph. It's hoped that this commit will help reduce the number of "unresolvable" dependency graphs we've seen in the wild and otherwise make Cargo a little more ergonomic to use as well. More details can be found in the source's comments! Closes #4127
2 parents be19a1b + 51d2356 commit b3df526

File tree

17 files changed

+656
-84
lines changed

17 files changed

+656
-84
lines changed

src/bin/commands/read_manifest.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use command_prelude::*;
22

3-
use cargo::core::Package;
43
use cargo::print_json;
54

65
pub fn cli() -> App {
@@ -13,8 +12,7 @@ Print a JSON representation of a Cargo.toml manifest.",
1312
}
1413

1514
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
16-
let root = args.root_manifest(config)?;
17-
let pkg = Package::for_path(&root, config)?;
18-
print_json(&pkg);
15+
let ws = args.workspace(config)?;
16+
print_json(&ws.current()?);
1917
Ok(())
2018
}

src/cargo/core/dependency.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -343,9 +343,8 @@ impl Dependency {
343343
}
344344

345345
/// Returns true if the package (`sum`) can fulfill this dependency request.
346-
pub fn matches_ignoring_source(&self, sum: &Summary) -> bool {
347-
self.name() == sum.package_id().name()
348-
&& self.version_req().matches(sum.package_id().version())
346+
pub fn matches_ignoring_source(&self, id: &PackageId) -> bool {
347+
self.name() == id.name() && self.version_req().matches(id.version())
349348
}
350349

351350
/// Returns true if the package (`id`) can fulfill this dependency request.

src/cargo/core/package.rs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use lazycell::LazyCell;
1212
use core::{Dependency, Manifest, PackageId, SourceId, Target};
1313
use core::{SourceMap, Summary};
1414
use core::interning::InternedString;
15-
use ops;
1615
use util::{internal, lev_distance, Config};
1716
use util::errors::{CargoResult, CargoResultExt};
1817

@@ -81,14 +80,6 @@ impl Package {
8180
}
8281
}
8382

84-
/// Calculate the Package from the manifest path (and cargo configuration).
85-
pub fn for_path(manifest_path: &Path, config: &Config) -> CargoResult<Package> {
86-
let path = manifest_path.parent().unwrap();
87-
let source_id = SourceId::for_path(path)?;
88-
let (pkg, _) = ops::read_package(manifest_path, &source_id, config)?;
89-
Ok(pkg)
90-
}
91-
9283
/// Get the manifest dependencies
9384
pub fn dependencies(&self) -> &[Dependency] {
9485
self.manifest.dependencies()

src/cargo/core/registry.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ impl<'cfg> Registry for PackageRegistry<'cfg> {
434434
patches.extend(
435435
extra
436436
.iter()
437-
.filter(|s| dep.matches_ignoring_source(s))
437+
.filter(|s| dep.matches_ignoring_source(s.package_id()))
438438
.cloned(),
439439
);
440440
}

src/cargo/core/resolver/encode.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use serde::ser;
66
use serde::de;
77

88
use core::{Dependency, Package, PackageId, SourceId, Workspace};
9-
use util::{internal, Config, Graph};
9+
use util::{internal, Graph};
1010
use util::errors::{CargoError, CargoResult, CargoResultExt};
1111

1212
use super::Resolve;
@@ -207,33 +207,33 @@ fn build_path_deps(ws: &Workspace) -> HashMap<String, SourceId> {
207207
visited.insert(member.package_id().source_id().clone());
208208
}
209209
for member in members.iter() {
210-
build_pkg(member, ws.config(), &mut ret, &mut visited);
210+
build_pkg(member, ws, &mut ret, &mut visited);
211211
}
212212
for deps in ws.root_patch().values() {
213213
for dep in deps {
214-
build_dep(dep, ws.config(), &mut ret, &mut visited);
214+
build_dep(dep, ws, &mut ret, &mut visited);
215215
}
216216
}
217217
for &(_, ref dep) in ws.root_replace() {
218-
build_dep(dep, ws.config(), &mut ret, &mut visited);
218+
build_dep(dep, ws, &mut ret, &mut visited);
219219
}
220220

221221
return ret;
222222

223223
fn build_pkg(
224224
pkg: &Package,
225-
config: &Config,
225+
ws: &Workspace,
226226
ret: &mut HashMap<String, SourceId>,
227227
visited: &mut HashSet<SourceId>,
228228
) {
229229
for dep in pkg.dependencies() {
230-
build_dep(dep, config, ret, visited);
230+
build_dep(dep, ws, ret, visited);
231231
}
232232
}
233233

234234
fn build_dep(
235235
dep: &Dependency,
236-
config: &Config,
236+
ws: &Workspace,
237237
ret: &mut HashMap<String, SourceId>,
238238
visited: &mut HashSet<SourceId>,
239239
) {
@@ -245,13 +245,13 @@ fn build_path_deps(ws: &Workspace) -> HashMap<String, SourceId> {
245245
Ok(p) => p.join("Cargo.toml"),
246246
Err(_) => return,
247247
};
248-
let pkg = match Package::for_path(&path, config) {
248+
let pkg = match ws.load(&path) {
249249
Ok(p) => p,
250250
Err(_) => return,
251251
};
252252
ret.insert(pkg.name().to_string(), pkg.package_id().source_id().clone());
253253
visited.insert(pkg.package_id().source_id().clone());
254-
build_pkg(&pkg, config, ret, visited);
254+
build_pkg(&pkg, ws, ret, visited);
255255
}
256256
}
257257

src/cargo/core/resolver/mod.rs

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -384,10 +384,39 @@ struct Context {
384384
type Activations = HashMap<(InternedString, SourceId), Rc<Vec<Summary>>>;
385385

386386
/// Builds the list of all packages required to build the first argument.
387+
///
388+
/// * `summaries` - the list of package summaries along with how to resolve
389+
/// their features. This is a list of all top-level packages that are intended
390+
/// to be part of the lock file (resolve output). These typically are a list
391+
/// of all workspace members.
392+
///
393+
/// * `replacements` - this is a list of `[replace]` directives found in the
394+
/// root of the workspace. The list here is a `PackageIdSpec` of what to
395+
/// replace and a `Dependency` to replace that with. In general it's not
396+
/// recommended to use `[replace]` any more and use `[patch]` instead, which
397+
/// is supported elsewhere.
398+
///
399+
/// * `registry` - this is the source from which all package summaries are
400+
/// loaded. It's expected that this is extensively configured ahead of time
401+
/// and is idempotent with our requests to it (aka returns the same results
402+
/// for the same query every time). Typically this is an instance of a
403+
/// `PackageRegistry`.
404+
///
405+
/// * `try_to_use` - this is a list of package ids which were previously found
406+
/// in the lock file. We heuristically prefer the ids listed in `try_to_use`
407+
/// when sorting candidates to activate, but otherwise this isn't used
408+
/// anywhere else.
409+
///
410+
/// * `config` - a location to print warnings and such, or `None` if no warnings
411+
/// should be printed
412+
///
413+
/// * `print_warnings` - whether or not to print backwards-compatibility
414+
/// warnings and such
387415
pub fn resolve(
388416
summaries: &[(Summary, Method)],
389417
replacements: &[(PackageIdSpec, Dependency)],
390418
registry: &mut Registry,
419+
try_to_use: &[&PackageId],
391420
config: Option<&Config>,
392421
print_warnings: bool,
393422
) -> CargoResult<Resolve> {
@@ -400,12 +429,8 @@ pub fn resolve(
400429
warnings: RcList::new(),
401430
};
402431
let _p = profile::start("resolving");
403-
let cx = activate_deps_loop(
404-
cx,
405-
&mut RegistryQueryer::new(registry, replacements),
406-
summaries,
407-
config,
408-
)?;
432+
let mut registry = RegistryQueryer::new(registry, replacements, try_to_use);
433+
let cx = activate_deps_loop(cx, &mut registry, summaries, config)?;
409434

410435
let mut resolve = Resolve {
411436
graph: cx.graph(),
@@ -637,16 +662,22 @@ impl ConflictReason {
637662
struct RegistryQueryer<'a> {
638663
registry: &'a mut (Registry + 'a),
639664
replacements: &'a [(PackageIdSpec, Dependency)],
665+
try_to_use: HashSet<&'a PackageId>,
640666
// TODO: with nll the Rc can be removed
641667
cache: HashMap<Dependency, Rc<Vec<Candidate>>>,
642668
}
643669

644670
impl<'a> RegistryQueryer<'a> {
645-
fn new(registry: &'a mut Registry, replacements: &'a [(PackageIdSpec, Dependency)]) -> Self {
671+
fn new(
672+
registry: &'a mut Registry,
673+
replacements: &'a [(PackageIdSpec, Dependency)],
674+
try_to_use: &'a [&'a PackageId],
675+
) -> Self {
646676
RegistryQueryer {
647677
registry,
648678
replacements,
649679
cache: HashMap::new(),
680+
try_to_use: try_to_use.iter().cloned().collect(),
650681
}
651682
}
652683

@@ -739,9 +770,17 @@ impl<'a> RegistryQueryer<'a> {
739770
candidate.replace = replace;
740771
}
741772

742-
// When we attempt versions for a package, we'll want to start at
743-
// the maximum version and work our way down.
744-
ret.sort_unstable_by(|a, b| b.summary.version().cmp(a.summary.version()));
773+
// When we attempt versions for a package we'll want to do so in a
774+
// sorted fashion to pick the "best candidates" first. Currently we try
775+
// prioritized summaries (those in `try_to_use`) and failing that we
776+
// list everything from the maximum version to the lowest version.
777+
ret.sort_unstable_by(|a, b| {
778+
let a_in_previous = self.try_to_use.contains(a.summary.package_id());
779+
let b_in_previous = self.try_to_use.contains(b.summary.package_id());
780+
let a = (a_in_previous, a.summary.version());
781+
let b = (b_in_previous, b.summary.version());
782+
a.cmp(&b).reverse()
783+
});
745784

746785
let out = Rc::new(ret);
747786

src/cargo/core/workspace.rs

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1-
use std::collections::hash_map::{Entry, HashMap};
1+
use std::cell::RefCell;
22
use std::collections::BTreeMap;
3+
use std::collections::hash_map::{Entry, HashMap};
34
use std::path::{Path, PathBuf};
45
use std::slice;
56

67
use glob::glob;
78
use url::Url;
89

10+
use core::registry::PackageRegistry;
911
use core::{EitherManifest, Package, SourceId, VirtualManifest};
1012
use core::{Dependency, PackageIdSpec, Profile, Profiles};
11-
use util::{Config, Filesystem};
13+
use ops;
14+
use sources::PathSource;
1215
use util::errors::{CargoResult, CargoResultExt};
1316
use util::paths;
1417
use util::toml::read_manifest;
18+
use util::{Config, Filesystem};
1519

1620
/// The core abstraction in Cargo for working with a workspace of crates.
1721
///
@@ -67,6 +71,10 @@ pub struct Workspace<'cfg> {
6771
// needed by the current configuration (such as in cargo install). In some
6872
// cases `false` also results in the non-enforcement of dev-dependencies.
6973
require_optional_deps: bool,
74+
75+
// A cache of lodaed packages for particular paths which is disjoint from
76+
// `packages` up above, used in the `load` method down below.
77+
loaded_packages: RefCell<HashMap<PathBuf, Package>>,
7078
}
7179

7280
// Separate structure for tracking loaded packages (to avoid loading anything
@@ -137,6 +145,7 @@ impl<'cfg> Workspace<'cfg> {
137145
default_members: Vec::new(),
138146
is_ephemeral: false,
139147
require_optional_deps: true,
148+
loaded_packages: RefCell::new(HashMap::new()),
140149
};
141150
ws.root_manifest = ws.find_root(manifest_path)?;
142151
ws.find_members()?;
@@ -172,6 +181,7 @@ impl<'cfg> Workspace<'cfg> {
172181
default_members: Vec::new(),
173182
is_ephemeral: true,
174183
require_optional_deps,
184+
loaded_packages: RefCell::new(HashMap::new()),
175185
};
176186
{
177187
let key = ws.current_manifest.parent().unwrap();
@@ -669,11 +679,63 @@ impl<'cfg> Workspace<'cfg> {
669679

670680
Ok(())
671681
}
682+
683+
pub fn load(&self, manifest_path: &Path) -> CargoResult<Package> {
684+
match self.packages.maybe_get(manifest_path) {
685+
Some(&MaybePackage::Package(ref p)) => return Ok(p.clone()),
686+
Some(&MaybePackage::Virtual(_)) => bail!("cannot load workspace root"),
687+
None => {}
688+
}
689+
690+
let mut loaded = self.loaded_packages.borrow_mut();
691+
if let Some(p) = loaded.get(manifest_path).cloned() {
692+
return Ok(p);
693+
}
694+
let source_id = SourceId::for_path(manifest_path.parent().unwrap())?;
695+
let (package, _nested_paths) = ops::read_package(manifest_path, &source_id, self.config)?;
696+
loaded.insert(manifest_path.to_path_buf(), package.clone());
697+
Ok(package)
698+
}
699+
700+
/// Preload the provided registry with already loaded packages.
701+
///
702+
/// A workspace may load packages during construction/parsing/early phases
703+
/// for various operations, and this preload step avoids doubly-loading and
704+
/// parsing crates on the filesystem by inserting them all into the registry
705+
/// with their in-memory formats.
706+
pub fn preload(&self, registry: &mut PackageRegistry<'cfg>) {
707+
// These can get weird as this generally represents a workspace during
708+
// `cargo install`. Things like git repositories will actually have a
709+
// `PathSource` with multiple entries in it, so the logic below is
710+
// mostly just an optimization for normal `cargo build` in workspaces
711+
// during development.
712+
if self.is_ephemeral {
713+
return;
714+
}
715+
716+
for pkg in self.packages.packages.values() {
717+
let pkg = match *pkg {
718+
MaybePackage::Package(ref p) => p.clone(),
719+
MaybePackage::Virtual(_) => continue,
720+
};
721+
let mut src = PathSource::new(
722+
pkg.manifest_path(),
723+
pkg.package_id().source_id(),
724+
self.config,
725+
);
726+
src.preload_with(pkg);
727+
registry.add_preloaded(Box::new(src));
728+
}
729+
}
672730
}
673731

674732
impl<'cfg> Packages<'cfg> {
675733
fn get(&self, manifest_path: &Path) -> &MaybePackage {
676-
&self.packages[manifest_path.parent().unwrap()]
734+
self.maybe_get(manifest_path).unwrap()
735+
}
736+
737+
fn maybe_get(&self, manifest_path: &Path) -> Option<&MaybePackage> {
738+
self.packages.get(manifest_path.parent().unwrap())
677739
}
678740

679741
fn load(&mut self, manifest_path: &Path) -> CargoResult<&MaybePackage> {

src/cargo/ops/cargo_rustc/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1082,7 +1082,7 @@ fn build_deps_args<'a, 'cfg>(
10821082
.pkg
10831083
.dependencies()
10841084
.iter()
1085-
.filter(|d| d.matches_ignoring_source(dep.pkg.summary()))
1085+
.filter(|d| d.matches_ignoring_source(dep.pkg.package_id()))
10861086
.filter_map(|d| d.rename())
10871087
.next();
10881088

src/cargo/ops/registry.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -447,8 +447,8 @@ pub fn modify_owners(config: &Config, opts: &OwnersOptions) -> CargoResult<()> {
447447
Some(ref name) => name.clone(),
448448
None => {
449449
let manifest_path = find_root_manifest_for_wd(None, config.cwd())?;
450-
let pkg = Package::for_path(&manifest_path, config)?;
451-
pkg.name().to_string()
450+
let ws = Workspace::new(&manifest_path, config)?;
451+
ws.current()?.package_id().name().to_string()
452452
}
453453
};
454454

@@ -508,8 +508,8 @@ pub fn yank(
508508
Some(name) => name,
509509
None => {
510510
let manifest_path = find_root_manifest_for_wd(None, config.cwd())?;
511-
let pkg = Package::for_path(&manifest_path, config)?;
512-
pkg.name().to_string()
511+
let ws = Workspace::new(&manifest_path, config)?;
512+
ws.current()?.package_id().name().to_string()
513513
}
514514
};
515515
let version = match version {

0 commit comments

Comments
 (0)