cargo/ops/resolve.rs
1//! High-level APIs for executing the resolver.
2//!
3//! This module provides functions for running the resolver given a workspace, including loading
4//! the `Cargo.lock` file and checking if it needs updating.
5//!
6//! There are roughly 3 main functions:
7//!
8//! - [`resolve_ws`]: A simple, high-level function with no options.
9//! - [`resolve_ws_with_opts`]: A medium-level function with options like
10//! user-provided features. This is the most appropriate function to use in
11//! most cases.
12//! - [`resolve_with_previous`]: A low-level function for running the resolver,
13//! providing the most power and flexibility.
14//!
15//! ### Data Structures
16//!
17//! - [`Workspace`]:
18//! Usually created by [`crate::util::command_prelude::ArgMatchesExt::workspace`] which discovers the root of the
19//! workspace, and loads all the workspace members as a [`Package`] object
20//! - [`Package`]
21//! Corresponds with `Cargo.toml` manifest (deserialized as [`Manifest`]) and its associated files.
22//! - [`Target`]s are crates such as the library, binaries, integration test, or examples.
23//! They are what is actually compiled by `rustc`.
24//! Each `Target` defines a crate root, like `src/lib.rs` or `examples/foo.rs`.
25//! - [`PackageId`] --- A unique identifier for a package.
26//! - [`PackageRegistry`]:
27//! The primary interface for how the dependency
28//! resolver finds packages. It contains the `SourceMap`, and handles things
29//! like the `[patch]` table. The dependency resolver
30//! sends a query to the `PackageRegistry` to "get me all packages that match
31//! this dependency declaration". The `Registry` trait provides a generic interface
32//! to the `PackageRegistry`, but this is only used for providing an alternate
33//! implementation of the `PackageRegistry` for testing.
34//! - [`SourceMap`]: Map of all available sources.
35//! - [`Source`]: An abstraction for something that can fetch packages (a remote
36//! registry, a git repo, the local filesystem, etc.). Check out the [source
37//! implementations] for all the details about registries, indexes, git
38//! dependencies, etc.
39//! * [`SourceId`]: A unique identifier for a source.
40//! - [`Summary`]: A of a [`Manifest`], and is essentially
41//! the information that can be found in a registry index. Queries against the
42//! `PackageRegistry` yields a `Summary`. The resolver uses the summary
43//! information to build the dependency graph.
44//! - [`PackageSet`] --- Contains all the `Package` objects. This works with the
45//! [`Downloads`] struct to coordinate downloading packages. It has a reference
46//! to the `SourceMap` to get the `Source` objects which tell the `Downloads`
47//! struct which URLs to fetch.
48//!
49//! [`Package`]: crate::workspace::package
50//! [`Target`]: crate::workspace::Target
51//! [`Manifest`]: crate::workspace::Manifest
52//! [`Source`]: crate::sources::source::Source
53//! [`SourceMap`]: crate::sources::source::SourceMap
54//! [`PackageRegistry`]: crate::workspace::registry::PackageRegistry
55//! [source implementations]: crate::sources
56//! [`Downloads`]: crate::workspace::package::Downloads
57
58use crate::compiler::{CompileKind, RustcTargetData};
59use crate::context::FeatureUnification;
60use crate::ops;
61use crate::resolver::PublishAgePolicy;
62use crate::resolver::features::{
63 CliFeatures, FeatureOpts, FeatureResolver, ForceAllTargets, RequestedFeatures, ResolvedFeatures,
64};
65use crate::resolver::{
66 self, HasDevUnits, Resolve, ResolveOpts, ResolveVersion, VersionOrdering, VersionPreferences,
67};
68use crate::sources::RecursivePathSource;
69use crate::util::CanonicalUrl;
70use crate::util::cache_lock::CacheLockMode;
71use crate::util::data_structures::{HashMap, HashSet};
72use crate::util::errors::CargoResult;
73use crate::workspace::Dependency;
74use crate::workspace::GitReference;
75use crate::workspace::PackageId;
76use crate::workspace::PackageIdSpec;
77use crate::workspace::PackageIdSpecQuery;
78use crate::workspace::PackageSet;
79use crate::workspace::SourceId;
80use crate::workspace::Workspace;
81use crate::workspace::registry::{LockedPatchDependency, PackageRegistry};
82use crate::workspace::summary::Summary;
83use anyhow::Context as _;
84use cargo_util::paths;
85use cargo_util_schemas::core::PartialVersion;
86use cargo_util_terminal::report::Group;
87use cargo_util_terminal::report::Level;
88use std::rc::Rc;
89use tracing::{debug, trace};
90
91/// Filter for keep using Package ID from previous lockfile.
92type Keep<'a> = &'a dyn Fn(&PackageId) -> bool;
93
94/// Result for `resolve_ws_with_opts`.
95pub struct WorkspaceResolve<'gctx> {
96 /// Packages to be downloaded.
97 pub pkg_set: PackageSet<'gctx>,
98 /// The resolve for the entire workspace.
99 ///
100 /// This may be `None` for things like `cargo install` and `-Zavoid-dev-deps`.
101 /// This does not include `paths` overrides.
102 pub workspace_resolve: Option<Resolve>,
103 /// The narrowed resolve, with the specific features enabled.
104 pub targeted_resolve: Resolve,
105 /// Package specs requested for compilation along with specific features enabled. This usually
106 /// has the length of one but there may be more specs with different features when using the
107 /// `package` feature resolver.
108 pub specs_and_features: Vec<SpecsAndResolvedFeatures>,
109}
110
111/// Pair of package specs requested for compilation along with enabled features.
112pub struct SpecsAndResolvedFeatures {
113 /// Packages that are supposed to be built.
114 pub specs: Vec<PackageIdSpec>,
115 /// The features activated per package.
116 pub resolved_features: ResolvedFeatures,
117}
118
119const UNUSED_PATCH_WARNING: &str = "\
120Check that the patched package version and available features are compatible
121with the dependency requirements. If the patch has a different version from
122what is locked in the Cargo.lock file, run `cargo update` to use the new
123version. This may also occur with an optional dependency that is not enabled.";
124
125/// Resolves all dependencies for the workspace using the previous
126/// lock file as a guide if present.
127///
128/// This function will also write the result of resolution as a new lock file
129/// (unless it is an ephemeral workspace such as `cargo install` or `cargo
130/// package`).
131///
132/// This is a simple interface used by commands like `clean`, `fetch`, and
133/// `package`, which don't specify any options or features.
134pub fn resolve_ws<'a>(ws: &Workspace<'a>, dry_run: bool) -> CargoResult<(PackageSet<'a>, Resolve)> {
135 let mut registry = ws.package_registry()?;
136 let resolve = resolve_with_registry(ws, &mut registry, dry_run)?;
137 let packages = get_resolved_packages(&resolve, registry)?;
138 Ok((packages, resolve))
139}
140
141/// Resolves dependencies for some packages of the workspace,
142/// taking into account `paths` overrides and activated features.
143///
144/// This function will also write the result of resolution as a new lock file
145/// (unless `Workspace::require_optional_deps` is false, such as `cargo
146/// install` or `-Z avoid-dev-deps`), or it is an ephemeral workspace (`cargo
147/// install` or `cargo package`).
148///
149/// `specs` may be empty, which indicates it should resolve all workspace
150/// members. In this case, `opts.all_features` must be `true`.
151pub fn resolve_ws_with_opts<'gctx>(
152 ws: &Workspace<'gctx>,
153 target_data: &mut RustcTargetData<'gctx>,
154 requested_targets: &[CompileKind],
155 cli_features: &CliFeatures,
156 specs: &[PackageIdSpec],
157 has_dev_units: HasDevUnits,
158 force_all_targets: ForceAllTargets,
159 dry_run: bool,
160) -> CargoResult<WorkspaceResolve<'gctx>> {
161 let feature_unification = ws.resolve_feature_unification();
162 let specs_to_resolve = match feature_unification {
163 FeatureUnification::Workspace => &ops::Packages::All(Vec::new()).to_package_id_specs(ws)?,
164 FeatureUnification::Selected | FeatureUnification::Package => specs,
165 };
166 let mut registry = ws.package_registry()?;
167 let (resolve, resolved_with_overrides) = if ws.require_optional_deps() {
168 // First, resolve the root_package's *listed* dependencies, as well as
169 // downloading and updating all remotes and such.
170 let resolve = resolve_with_registry(ws, &mut registry, dry_run)?;
171 // No need to add patches again, `resolve_with_registry` has done it.
172 let add_patches = false;
173
174 // Second, resolve with precisely what we're doing. Filter out
175 // transitive dependencies if necessary, specify features, handle
176 // overrides, etc.
177 add_overrides(&mut registry, ws)?;
178
179 for (replace_spec, dep) in ws.root_replace() {
180 if !resolve
181 .iter()
182 .any(|r| replace_spec.matches(r) && !dep.matches_id(r))
183 {
184 ws.gctx()
185 .shell()
186 .warn(format!("package replacement is not used: {}", replace_spec))?
187 }
188
189 let mut unused_fields = Vec::new();
190 if dep.features().len() != 0 {
191 unused_fields.push("`features`");
192 }
193 if !dep.uses_default_features() {
194 unused_fields.push("`default-features`")
195 }
196 if !unused_fields.is_empty() {
197 ws.gctx().shell().print_report(
198 &[Level::WARNING
199 .secondary_title(format!(
200 "unused field in replacement for `{}`: {}",
201 dep.package_name(),
202 unused_fields.join(", ")
203 ))
204 .element(Level::NOTE.message(format!(
205 "configure {} in the `dependencies` entry",
206 unused_fields.join(", ")
207 )))],
208 false,
209 )?;
210 }
211 }
212
213 let resolved_with_overrides = resolve_with_previous(
214 &mut registry,
215 ws,
216 cli_features,
217 has_dev_units,
218 Some(&resolve),
219 None,
220 &specs_to_resolve,
221 add_patches,
222 )?;
223 (Some(resolve), resolved_with_overrides)
224 } else {
225 let add_patches = true;
226 let resolve = ops::load_pkg_lockfile(ws)?;
227 let resolved_with_overrides = resolve_with_previous(
228 &mut registry,
229 ws,
230 cli_features,
231 has_dev_units,
232 resolve.as_ref(),
233 None,
234 &specs_to_resolve,
235 add_patches,
236 )?;
237 // Skipping `print_lockfile_changes` as there are cases where this prints irrelevant
238 // information
239 (resolve, resolved_with_overrides)
240 };
241
242 let pkg_set = get_resolved_packages(&resolved_with_overrides, registry)?;
243
244 let members_with_features = ws.members_with_features(&specs_to_resolve, cli_features)?;
245 let member_ids = members_with_features
246 .iter()
247 .map(|(p, _fts)| p.package_id())
248 .collect::<Vec<_>>();
249
250 // Artifact dependencies can introduce compile kinds (the artifact's
251 // `target`) beyond those gathered up front from the workspace members in
252 // `RustcTargetData::new`. When such an artifact dependency is reached only
253 // transitively through a non-member dependency, its target is otherwise
254 // unknown, and traversing the resolve graph below would panic looking the
255 // target info up (e.g. when evaluating a `cfg(..)` for that platform).
256 // Register those kinds now that the full graph is resolved.
257 for pkg_id in resolved_with_overrides.iter() {
258 for kind in resolved_with_overrides
259 .bindeps(pkg_id)
260 .filter_map(|(_dep_id, dep)| dep.artifact()?.target()?.to_compile_kind())
261 {
262 // Best effort: an invalid target tuple is reported later,
263 // with proper context, while building the unit graph, so
264 // any error here is intentionally ignored.
265 let _ = target_data.merge_compile_kind(kind);
266 }
267 }
268
269 pkg_set.download_accessible(
270 &resolved_with_overrides,
271 &member_ids,
272 has_dev_units,
273 requested_targets,
274 target_data,
275 force_all_targets,
276 )?;
277
278 let specs_and_features = match feature_unification {
279 // We want to narrow the features to the current specs so that stuff like `cargo check -p a
280 // -p b -F a/a,b/b` works and the resolver does not contain that `a` does not have feature
281 // `b` and vice-versa. However, resolver v1 needs to see even features of unselected
282 // packages turned on if it was because of working directory being inside the unselected
283 // package, because they might turn on a feature of a selected package.
284 FeatureUnification::Package => specs_to_resolve
285 .iter()
286 .map(|package| {
287 let mut narrowed_features = cli_features.clone();
288 let enabled_features = members_with_features
289 .iter()
290 .filter_map(|(member, cli_features)| {
291 package
292 .matches(member.package_id())
293 .then_some(cli_features.features.iter())
294 })
295 .flatten()
296 .cloned()
297 .collect();
298 narrowed_features.features = Rc::new(enabled_features);
299
300 Ok(SpecsAndResolvedFeatures {
301 specs: vec![package.clone()],
302 resolved_features: FeatureResolver::resolve(
303 ws,
304 target_data,
305 &resolved_with_overrides,
306 &pkg_set,
307 &narrowed_features,
308 std::slice::from_ref(package),
309 requested_targets,
310 FeatureOpts::new(ws, has_dev_units, force_all_targets)?,
311 )?,
312 })
313 })
314 .collect::<CargoResult<Vec<SpecsAndResolvedFeatures>>>()?,
315 FeatureUnification::Selected | FeatureUnification::Workspace => {
316 vec![SpecsAndResolvedFeatures {
317 specs: specs.to_vec(),
318 resolved_features: FeatureResolver::resolve(
319 ws,
320 target_data,
321 &resolved_with_overrides,
322 &pkg_set,
323 cli_features,
324 specs_to_resolve,
325 requested_targets,
326 FeatureOpts::new(ws, has_dev_units, force_all_targets)?,
327 )?,
328 }]
329 }
330 };
331
332 pkg_set.warn_no_lib_packages_and_artifact_libs_overlapping_deps(
333 ws,
334 &resolved_with_overrides,
335 &member_ids,
336 has_dev_units,
337 requested_targets,
338 target_data,
339 force_all_targets,
340 )?;
341
342 Ok(WorkspaceResolve {
343 pkg_set,
344 workspace_resolve: resolve,
345 targeted_resolve: resolved_with_overrides,
346 specs_and_features,
347 })
348}
349
350#[tracing::instrument(skip_all)]
351fn resolve_with_registry<'gctx>(
352 ws: &Workspace<'gctx>,
353 registry: &mut PackageRegistry<'gctx>,
354 dry_run: bool,
355) -> CargoResult<Resolve> {
356 let prev = ops::load_pkg_lockfile(ws)?;
357 let mut resolve = resolve_with_previous(
358 registry,
359 ws,
360 &CliFeatures::new_all(true),
361 HasDevUnits::Yes,
362 prev.as_ref(),
363 None,
364 &[],
365 true,
366 )?;
367
368 let print = if !ws.is_ephemeral() && ws.require_optional_deps() {
369 if !dry_run {
370 ops::write_pkg_lockfile(ws, &mut resolve)?
371 } else {
372 true
373 }
374 } else {
375 // This mostly represents
376 // - `cargo install --locked` and the only change is the package is no longer local but
377 // from the registry which is noise
378 // - publish of libraries
379 false
380 };
381 if print {
382 ops::print_lockfile_changes(ws, prev.as_ref(), &resolve, registry)?;
383 }
384 Ok(resolve)
385}
386
387/// Resolves all dependencies for a package using an optional previous instance
388/// of resolve to guide the resolution process.
389///
390/// This also takes an optional filter `keep_previous`, which informs the `registry`
391/// which package ID should be locked to the previous instance of resolve
392/// (often used in pairings with updates). See comments in [`register_previous_locks`]
393/// for scenarios that might override this.
394///
395/// The previous resolve normally comes from a lock file. This function does not
396/// read or write lock files from the filesystem.
397///
398/// `specs` may be empty, which indicates it should resolve all workspace
399/// members. In this case, `opts.all_features` must be `true`.
400///
401/// If `register_patches` is true, then entries from the `[patch]` table in
402/// the manifest will be added to the given `PackageRegistry`.
403#[tracing::instrument(skip_all)]
404pub fn resolve_with_previous<'gctx>(
405 registry: &mut PackageRegistry<'gctx>,
406 ws: &Workspace<'gctx>,
407 cli_features: &CliFeatures,
408 has_dev_units: HasDevUnits,
409 previous: Option<&Resolve>,
410 keep_previous: Option<Keep<'_>>,
411 specs: &[PackageIdSpec],
412 register_patches: bool,
413) -> CargoResult<Resolve> {
414 // We only want one Cargo at a time resolving a crate graph since this can
415 // involve a lot of frobbing of the global caches.
416 let _lock = ws
417 .gctx()
418 .acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
419
420 // Some packages are already loaded when setting up a workspace. This
421 // makes it so anything that was already loaded will not be loaded again.
422 // Without this there were cases where members would be parsed multiple times
423 ws.preload(registry);
424
425 // In case any members were not already loaded or the Workspace is_ephemeral.
426 for member in ws.members() {
427 registry.add_sources(Some(member.package_id().source_id()))?;
428 }
429
430 // Try to keep all from previous resolve if no instruction given.
431 let keep_previous = keep_previous.unwrap_or(&|_| true);
432
433 // While registering patches, we will record preferences for particular versions
434 // of various packages.
435 let mut version_prefs = VersionPreferences::default();
436 if ws.gctx().cli_unstable().minimal_versions {
437 version_prefs.version_ordering(VersionOrdering::MinimumVersionsFirst)
438 }
439 if ws.resolve_honors_rust_version() {
440 let mut rust_versions: Vec<_> = ws
441 .members()
442 .filter_map(|p| p.rust_version().map(|rv| rv.to_partial()))
443 .collect();
444 if rust_versions.is_empty() {
445 let rustc = ws.gctx().load_global_rustc(Some(ws))?;
446 let rust_version: PartialVersion = rustc.version.clone().into();
447 rust_versions.push(rust_version);
448 }
449 version_prefs.rust_versions(rust_versions);
450 }
451 if let Some(publish_time) = ws.resolve_publish_time() {
452 version_prefs.publish_time(publish_time);
453 }
454 if ws.resolve_honors_publish_age() {
455 if let Some(policy) = PublishAgePolicy::new(ws.resolve_publish_time(), ws.gctx())? {
456 version_prefs.publish_age(policy);
457 }
458 }
459
460 let avoid_patch_ids = if register_patches {
461 register_patch_entries(registry, ws, previous, &mut version_prefs, keep_previous)?
462 } else {
463 HashSet::default()
464 };
465
466 // Refine `keep` with patches that should avoid locking.
467 let keep = |p: &PackageId| keep_previous(p) && !avoid_patch_ids.contains(p);
468
469 let dev_deps = ws.require_optional_deps() || has_dev_units == HasDevUnits::Yes;
470
471 if let Some(r) = previous {
472 trace!("previous: {:?}", r);
473
474 // In the case where a previous instance of resolve is available, we
475 // want to lock as many packages as possible to the previous version
476 // without disturbing the graph structure.
477 register_previous_locks(ws, registry, r, &keep, dev_deps);
478
479 // Prefer to use anything in the previous lock file, aka we want to have conservative updates.
480 let _span = tracing::span!(tracing::Level::TRACE, "prefer_package_id").entered();
481 for id in r.iter().filter(keep) {
482 debug!("attempting to prefer {}", id);
483 version_prefs.prefer_package_id(id);
484 }
485 }
486
487 if register_patches {
488 registry.lock_patches();
489 }
490
491 let summaries: Vec<(Summary, ResolveOpts)> = {
492 let _span = tracing::span!(tracing::Level::TRACE, "registry.lock").entered();
493 ws.members_with_features(specs, cli_features)?
494 .into_iter()
495 .map(|(member, features)| {
496 let summary = registry.lock(member.summary().clone());
497 (
498 summary,
499 ResolveOpts {
500 dev_deps,
501 features: RequestedFeatures::CliFeatures(features),
502 },
503 )
504 })
505 .collect()
506 };
507
508 let replace = lock_replacements(ws, previous, &keep);
509
510 let mut resolved = resolver::resolve(
511 &summaries,
512 &replace,
513 registry,
514 &version_prefs,
515 ResolveVersion::with_rust_version(ws.lowest_rust_version()),
516 ws.gctx(),
517 )?;
518
519 let patches = registry.patches().values().flat_map(|v| v.iter());
520 resolved.register_used_patches(patches);
521
522 if register_patches && !resolved.unused_patches().is_empty() {
523 emit_warnings_of_unused_patches(ws, &resolved, registry)?;
524 }
525
526 if let Some(previous) = previous {
527 resolved.merge_from(previous)?;
528 }
529 let gctx = ws.gctx();
530 let mut deferred = gctx.deferred_global_last_use()?;
531 deferred.save_no_error(gctx);
532 Ok(resolved)
533}
534
535/// Read the `paths` configuration variable to discover all path overrides that
536/// have been configured.
537#[tracing::instrument(skip_all)]
538pub fn add_overrides<'a>(
539 registry: &mut PackageRegistry<'a>,
540 ws: &Workspace<'a>,
541) -> CargoResult<()> {
542 let gctx = ws.gctx();
543 let Some(paths) = gctx.paths_overrides()? else {
544 return Ok(());
545 };
546
547 let paths = paths.val.iter().map(|(s, def)| {
548 // The path listed next to the string is the config file in which the
549 // key was located, so we want to pop off the `.cargo/config` component
550 // to get the directory containing the `.cargo` folder.
551 (paths::normalize_path(&def.root(gctx.cwd()).join(s)), def)
552 });
553
554 for (path, definition) in paths {
555 let id = SourceId::for_path(&path)?;
556 let source = RecursivePathSource::new(&path, id, ws.gctx());
557 source.load().with_context(|| {
558 format!(
559 "failed to update path override `{}` \
560 (defined in `{}`)",
561 path.display(),
562 definition
563 )
564 })?;
565 registry.add_override(Box::new(source));
566 }
567 Ok(())
568}
569
570pub fn get_resolved_packages<'gctx>(
571 resolve: &Resolve,
572 registry: PackageRegistry<'gctx>,
573) -> CargoResult<PackageSet<'gctx>> {
574 let ids: Vec<PackageId> = resolve.iter().collect();
575 registry.get(&ids)
576}
577
578/// In this function we're responsible for informing the `registry` of all
579/// locked dependencies from the previous lock file we had, `resolve`.
580///
581/// This gets particularly tricky for a couple of reasons. The first is that we
582/// want all updates to be conservative, so we actually want to take the
583/// `resolve` into account (and avoid unnecessary registry updates and such).
584/// the second, however, is that we want to be resilient to updates of
585/// manifests. For example if a dependency is added or a version is changed we
586/// want to make sure that we properly re-resolve (conservatively) instead of
587/// providing an opaque error.
588///
589/// The logic here is somewhat subtle, but there should be more comments below to
590/// clarify things.
591///
592/// Note that this function, at the time of this writing, is basically the
593/// entire fix for issue #4127.
594#[tracing::instrument(skip_all)]
595fn register_previous_locks(
596 ws: &Workspace<'_>,
597 registry: &mut PackageRegistry<'_>,
598 resolve: &Resolve,
599 keep: Keep<'_>,
600 dev_deps: bool,
601) {
602 let path_pkg = |id: SourceId| {
603 if !id.is_path() {
604 return None;
605 }
606 if let Ok(path) = id.url().to_file_path() {
607 if let Ok(pkg) = ws.load(&path.join("Cargo.toml")) {
608 return Some(pkg);
609 }
610 }
611 None
612 };
613
614 // Ok so we've been passed in a `keep` function which basically says "if I
615 // return `true` then this package wasn't listed for an update on the command
616 // line". That is, if we run `cargo update foo` then `keep(bar)` will return
617 // `true`, whereas `keep(foo)` will return `false` (roughly speaking).
618 //
619 // This isn't actually quite what we want, however. Instead we want to
620 // further refine this `keep` function with *all transitive dependencies* of
621 // the packages we're not keeping. For example, consider a case like this:
622 //
623 // * There's a crate `log`.
624 // * There's a crate `serde` which depends on `log`.
625 //
626 // Let's say we then run `cargo update serde`. This may *also* want to
627 // update the `log` dependency as our newer version of `serde` may have a
628 // new minimum version required for `log`. Now this isn't always guaranteed
629 // to work. What'll happen here is we *won't* lock the `log` dependency nor
630 // the `log` crate itself, but we will inform the registry "please prefer
631 // this version of `log`". That way if our newer version of serde works with
632 // the older version of `log`, we conservatively won't update `log`. If,
633 // however, nothing else in the dependency graph depends on `log` and the
634 // newer version of `serde` requires a new version of `log` it'll get pulled
635 // in (as we didn't accidentally lock it to an old version).
636 let mut avoid_locking = HashSet::default();
637 for node in resolve.iter() {
638 if !keep(&node) {
639 add_deps(resolve, node, &mut avoid_locking);
640 }
641 }
642
643 // Ok, but the above loop isn't the entire story! Updates to the dependency
644 // graph can come from two locations, the `cargo update` command or
645 // manifests themselves. For example a manifest on the filesystem may
646 // have been updated to have an updated version requirement on `serde`. In
647 // this case both `keep(serde)` and `keep(log)` return `true` (the `keep`
648 // that's an argument to this function). We, however, don't want to keep
649 // either of those! Otherwise we'll get obscure resolve errors about locked
650 // versions.
651 //
652 // To solve this problem we iterate over all packages with path sources
653 // (aka ones with manifests that are changing) and take a look at all of
654 // their dependencies. If any dependency does not match something in the
655 // previous lock file, then we're guaranteed that the main resolver will
656 // update the source of this dependency no matter what. Knowing this we
657 // poison all packages from the same source, forcing them all to get
658 // updated.
659 //
660 // This may seem like a heavy hammer, and it is! It means that if you change
661 // anything from crates.io then all of crates.io becomes unlocked. Note,
662 // however, that we still want conservative updates. This currently happens
663 // because the first candidate the resolver picks is the previously locked
664 // version, and only if that fails to activate to we move on and try
665 // a different version. (giving the guise of conservative updates)
666 //
667 // For example let's say we had `serde = "0.1"` written in our lock file.
668 // When we later edit this to `serde = "0.1.3"` we don't want to lock serde
669 // at its old version, 0.1.1. Instead we want to allow it to update to
670 // `0.1.3` and update its own dependencies (like above). To do this *all
671 // crates from crates.io* are not locked (aka added to `avoid_locking`).
672 // For dependencies like `log` their previous version in the lock file will
673 // come up first before newer version, if newer version are available.
674 {
675 let _span = tracing::span!(tracing::Level::TRACE, "poison").entered();
676 let mut path_deps = ws.members().cloned().collect::<Vec<_>>();
677 let mut visited = HashSet::default();
678 while let Some(member) = path_deps.pop() {
679 if !visited.insert(member.package_id()) {
680 continue;
681 }
682 let is_ws_member = ws.is_member(&member);
683 for dep in member.dependencies() {
684 // If this dependency didn't match anything special then we may want
685 // to poison the source as it may have been added. If this path
686 // dependencies is **not** a workspace member, however, and it's an
687 // optional/non-transitive dependency then it won't be necessarily
688 // be in our lock file. If this shows up then we avoid poisoning
689 // this source as otherwise we'd repeatedly update the registry.
690 //
691 // TODO: this breaks adding an optional dependency in a
692 // non-workspace member and then simultaneously editing the
693 // dependency on that crate to enable the feature. For now,
694 // this bug is better than the always-updating registry though.
695 if !is_ws_member && (dep.is_optional() || !dep.is_transitive()) {
696 continue;
697 }
698
699 // If dev-dependencies aren't being resolved, skip them.
700 if !dep.is_transitive() && !dev_deps {
701 continue;
702 }
703
704 // If this is a path dependency, then try to push it onto our
705 // worklist.
706 if let Some(pkg) = path_pkg(dep.source_id()) {
707 path_deps.push(pkg);
708 continue;
709 }
710
711 // If we match *anything* in the dependency graph then we consider
712 // ourselves all ok, and assume that we'll resolve to that.
713 if resolve.iter().any(|id| dep.matches_ignoring_source(id)) {
714 continue;
715 }
716
717 // Ok if nothing matches, then we poison the source of these
718 // dependencies and the previous lock file.
719 debug!(
720 "poisoning {} because {} looks like it changed {}",
721 dep.source_id(),
722 member.package_id(),
723 dep.package_name()
724 );
725 for id in resolve
726 .iter()
727 .filter(|id| id.source_id() == dep.source_id())
728 {
729 add_deps(resolve, id, &mut avoid_locking);
730 }
731 }
732 }
733 }
734
735 // Additionally, here we process all path dependencies listed in the previous
736 // resolve. They can not only have their dependencies change but also
737 // the versions of the package change as well. If this ends up happening
738 // then we want to make sure we don't lock a package ID node that doesn't
739 // actually exist. Note that we don't do transitive visits of all the
740 // package's dependencies here as that'll be covered below to poison those
741 // if they changed.
742 //
743 // This must come after all other `add_deps` calls to ensure it recursively walks the tree when
744 // called.
745 for node in resolve.iter() {
746 if let Some(pkg) = path_pkg(node.source_id()) {
747 if pkg.package_id() != node {
748 avoid_locking.insert(node);
749 }
750 }
751 }
752
753 // Alright now that we've got our new, fresh, shiny, and refined `keep`
754 // function let's put it to action. Take a look at the previous lock file,
755 // filter everything by this callback, and then shove everything else into
756 // the registry as a locked dependency.
757 let keep = |id: &PackageId| keep(id) && !avoid_locking.contains(id);
758
759 registry.clear_lock();
760 {
761 let _span = tracing::span!(tracing::Level::TRACE, "register_lock").entered();
762 for node in resolve.iter().filter(keep) {
763 let deps = resolve
764 .deps_not_replaced(node)
765 .map(|p| p.0)
766 .filter(keep)
767 .collect::<Vec<_>>();
768
769 // In the v2 lockfile format and prior the `branch=master` dependency
770 // directive was serialized the same way as the no-branch-listed
771 // directive. Nowadays in Cargo, however, these two directives are
772 // considered distinct and are no longer represented the same way. To
773 // maintain compatibility with older lock files we register locked nodes
774 // for *both* the master branch and the default branch.
775 //
776 // Note that this is only applicable for loading older resolves now at
777 // this point. All new lock files are encoded as v3-or-later, so this is
778 // just compat for loading an old lock file successfully.
779 if let Some(node) = master_branch_git_source(node, resolve) {
780 registry.register_lock(node, deps.clone());
781 }
782
783 registry.register_lock(node, deps);
784 }
785 }
786
787 /// Recursively add `node` and all its transitive dependencies to `set`.
788 fn add_deps(resolve: &Resolve, node: PackageId, set: &mut HashSet<PackageId>) {
789 if !set.insert(node) {
790 return;
791 }
792 debug!("ignoring any lock pointing directly at {}", node);
793 for (dep, _) in resolve.deps_not_replaced(node) {
794 add_deps(resolve, dep, set);
795 }
796 }
797}
798
799fn master_branch_git_source(id: PackageId, resolve: &Resolve) -> Option<PackageId> {
800 if resolve.version() <= ResolveVersion::V2 {
801 let source = id.source_id();
802 if let Some(GitReference::DefaultBranch) = source.git_reference() {
803 let new_source =
804 SourceId::for_git(source.url(), GitReference::Branch("master".to_string()))
805 .unwrap()
806 .with_precise_from(source);
807 return Some(id.with_source_id(new_source));
808 }
809 }
810 None
811}
812
813/// Emits warnings of unused patches case by case.
814///
815/// This function does its best to provide more targeted and helpful
816/// (such as showing close candidates that failed to match). However, that's
817/// not terribly easy to do, so just show a general help message if we cannot.
818fn emit_warnings_of_unused_patches(
819 ws: &Workspace<'_>,
820 resolve: &Resolve,
821 registry: &PackageRegistry<'_>,
822) -> CargoResult<()> {
823 const MESSAGE: &str = "was not used in the crate graph";
824
825 // Patch package with the source URLs being patch
826 let mut patch_pkgid_to_urls = HashMap::default();
827 for (url, summaries) in registry.patches().iter() {
828 for summary in summaries.iter() {
829 patch_pkgid_to_urls
830 .entry(summary.package_id())
831 .or_insert_with(HashSet::default)
832 .insert(url);
833 }
834 }
835
836 // pkg name -> all source IDs of under the same pkg name
837 let mut source_ids_grouped_by_pkg_name = HashMap::default();
838 for pkgid in resolve.iter() {
839 source_ids_grouped_by_pkg_name
840 .entry(pkgid.name())
841 .or_insert_with(HashSet::default)
842 .insert(pkgid.source_id());
843 }
844
845 let mut unemitted_unused_patches = Vec::new();
846 for unused in resolve.unused_patches().iter() {
847 // Show alternative source URLs if the source URLs being patched
848 // cannot be found in the crate graph.
849 match (
850 source_ids_grouped_by_pkg_name.get(&unused.name()),
851 patch_pkgid_to_urls.get(unused),
852 ) {
853 (Some(ids), Some(patched_urls))
854 if ids
855 .iter()
856 .all(|id| !patched_urls.contains(id.canonical_url())) =>
857 {
858 let mut help = "perhaps you meant one of the following:".to_owned();
859 for id in ids {
860 help.push_str("\n\t");
861 help.push_str(&id.display_registry_name());
862 }
863 ws.gctx().shell().print_report(
864 &[Level::WARNING
865 .secondary_title(format!("patch `{unused}` {MESSAGE}"))
866 .element(Level::HELP.message(help))],
867 false,
868 )?;
869 }
870 _ => unemitted_unused_patches.push(unused),
871 }
872 }
873
874 // Show general help message.
875 if !unemitted_unused_patches.is_empty() {
876 let mut warnings: Vec<_> = unemitted_unused_patches
877 .iter()
878 .map(|pkgid| {
879 Group::with_title(
880 Level::WARNING.secondary_title(format!("patch `{pkgid}` {MESSAGE}")),
881 )
882 })
883 .collect();
884 warnings.push(Group::with_title(
885 Level::HELP.secondary_title(UNUSED_PATCH_WARNING),
886 ));
887 ws.gctx().shell().print_report(&warnings, false)?;
888 }
889
890 Ok(())
891}
892
893/// Informs `registry` and `version_pref` that `[patch]` entries are available
894/// and preferable for the dependency resolution.
895///
896/// This returns a set of PackageIds of `[patch]` entries, and some related
897/// locked PackageIds, for which locking should be avoided (but which will be
898/// preferred when searching dependencies, via [`VersionPreferences::prefer_patch_deps`]).
899#[tracing::instrument(level = "debug", skip_all, ret)]
900fn register_patch_entries(
901 registry: &mut PackageRegistry<'_>,
902 ws: &Workspace<'_>,
903 previous: Option<&Resolve>,
904 version_prefs: &mut VersionPreferences,
905 keep_previous: Keep<'_>,
906) -> CargoResult<HashSet<PackageId>> {
907 let mut avoid_patch_ids = HashSet::default();
908 for (url, patches) in ws.root_patch()?.iter() {
909 for patch in patches {
910 version_prefs.prefer_dependency(patch.dep.clone());
911 }
912 let Some(previous) = previous else {
913 let patches: Vec<_> = patches.iter().map(|p| (p, None)).collect();
914 let unlock_ids = registry.patch(url, &patches)?;
915 // Since nothing is locked, this shouldn't possibly return anything.
916 assert!(unlock_ids.is_empty());
917 continue;
918 };
919
920 // This is a list of pairs where the first element of the pair is
921 // the raw `Dependency` which matches what's listed in `Cargo.toml`.
922 // The second element is, if present, the "locked" version of
923 // the `Dependency` as well as the `PackageId` that it previously
924 // resolved to. This second element is calculated by looking at the
925 // previous resolve graph, which is primarily what's done here to
926 // build the `registrations` list.
927 let mut registrations = Vec::new();
928 for patch in patches {
929 let dep = &patch.dep;
930 let candidates = || {
931 previous
932 .iter()
933 .chain(previous.unused_patches().iter().cloned())
934 .filter(&keep_previous)
935 };
936
937 let lock = match candidates().find(|id| dep.matches_id(*id)) {
938 // If we found an exactly matching candidate in our list of
939 // candidates, then that's the one to use.
940 Some(package_id) => {
941 let mut locked_dep = dep.clone();
942 locked_dep.lock_to(package_id);
943 Some(LockedPatchDependency {
944 dependency: locked_dep,
945 package_id,
946 alt_package_id: None,
947 })
948 }
949 None => {
950 // If the candidate does not have a matching source id
951 // then we may still have a lock candidate. If we're
952 // loading a v2-encoded resolve graph and `dep` is a
953 // git dep with `branch = 'master'`, then this should
954 // also match candidates without `branch = 'master'`
955 // (which is now treated separately in Cargo).
956 //
957 // In this scenario we try to convert candidates located
958 // in the resolve graph to explicitly having the
959 // `master` branch (if they otherwise point to
960 // `DefaultBranch`). If this works and our `dep`
961 // matches that then this is something we'll lock to.
962 match candidates().find(|&id| match master_branch_git_source(id, previous) {
963 Some(id) => dep.matches_id(id),
964 None => false,
965 }) {
966 Some(id_using_default) => {
967 let id_using_master = id_using_default.with_source_id(
968 dep.source_id()
969 .with_precise_from(id_using_default.source_id()),
970 );
971
972 let mut locked_dep = dep.clone();
973 locked_dep.lock_to(id_using_master);
974 Some(LockedPatchDependency {
975 dependency: locked_dep,
976 package_id: id_using_master,
977 // Note that this is where the magic
978 // happens, where the resolve graph
979 // probably has locks pointing to
980 // DefaultBranch sources, and by including
981 // this here those will get transparently
982 // rewritten to Branch("master") which we
983 // have a lock entry for.
984 alt_package_id: Some(id_using_default),
985 })
986 }
987
988 // No locked candidate was found
989 None => None,
990 }
991 }
992 };
993
994 registrations.push((patch, lock));
995 }
996
997 let canonical = CanonicalUrl::new(url)?;
998 for (orig_patch, unlock_id) in registry.patch(url, ®istrations)? {
999 // Avoid the locked patch ID.
1000 avoid_patch_ids.insert(unlock_id);
1001 // Also avoid the thing it is patching.
1002 avoid_patch_ids.extend(previous.iter().filter(|id| {
1003 orig_patch.dep.matches_ignoring_source(*id)
1004 && *id.source_id().canonical_url() == canonical
1005 }));
1006 }
1007 }
1008
1009 Ok(avoid_patch_ids)
1010}
1011
1012/// Locks each `[replace]` entry to a specific Package ID
1013/// if the lockfile contains any corresponding previous replacement.
1014fn lock_replacements(
1015 ws: &Workspace<'_>,
1016 previous: Option<&Resolve>,
1017 keep: Keep<'_>,
1018) -> Vec<(PackageIdSpec, Dependency)> {
1019 let root_replace = ws.root_replace();
1020 let replace = match previous {
1021 Some(r) => root_replace
1022 .iter()
1023 .map(|(spec, dep)| {
1024 for (&key, &val) in r.replacements().iter() {
1025 if spec.matches(key) && dep.matches_id(val) && keep(&val) {
1026 let mut dep = dep.clone();
1027 dep.lock_to(val);
1028 return (spec.clone(), dep);
1029 }
1030 }
1031 (spec.clone(), dep.clone())
1032 })
1033 .collect::<Vec<_>>(),
1034 None => root_replace.to_vec(),
1035 };
1036 replace
1037}