Skip to main content

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