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