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