cargo/ops/cargo_add/
mod.rs

1//! Core of cargo-add command
2
3mod crate_spec;
4
5use std::collections::BTreeMap;
6use std::collections::BTreeSet;
7use std::collections::VecDeque;
8use std::fmt::Write;
9use std::path::Path;
10use std::str::FromStr;
11
12use anyhow::Context as _;
13use cargo_util::paths;
14use cargo_util_schemas::core::PartialVersion;
15use cargo_util_schemas::manifest::PathBaseName;
16use cargo_util_schemas::manifest::RustVersion;
17use indexmap::IndexSet;
18use itertools::Itertools;
19use toml_edit::Item as TomlItem;
20
21use crate::core::dependency::DepKind;
22use crate::core::registry::PackageRegistry;
23use crate::core::FeatureValue;
24use crate::core::Features;
25use crate::core::Package;
26use crate::core::Registry;
27use crate::core::Shell;
28use crate::core::Summary;
29use crate::core::Workspace;
30use crate::sources::source::QueryKind;
31use crate::util::cache_lock::CacheLockMode;
32use crate::util::style;
33use crate::util::toml::lookup_path_base;
34use crate::util::toml_mut::dependency::Dependency;
35use crate::util::toml_mut::dependency::GitSource;
36use crate::util::toml_mut::dependency::MaybeWorkspace;
37use crate::util::toml_mut::dependency::PathSource;
38use crate::util::toml_mut::dependency::Source;
39use crate::util::toml_mut::dependency::WorkspaceSource;
40use crate::util::toml_mut::manifest::DepTable;
41use crate::util::toml_mut::manifest::LocalManifest;
42use crate::CargoResult;
43use crate::GlobalContext;
44use crate_spec::CrateSpec;
45
46const MAX_FEATURE_PRINTS: usize = 30;
47
48/// Information on what dependencies should be added
49#[derive(Clone, Debug)]
50pub struct AddOptions<'a> {
51    /// Configuration information for cargo operations
52    pub gctx: &'a GlobalContext,
53    /// Package to add dependencies to
54    pub spec: &'a Package,
55    /// Dependencies to add or modify
56    pub dependencies: Vec<DepOp>,
57    /// Which dependency section to add these to
58    pub section: DepTable,
59    /// Act as if dependencies will be added
60    pub dry_run: bool,
61    /// Whether the minimum supported Rust version should be considered during resolution
62    pub honor_rust_version: Option<bool>,
63}
64
65/// Add dependencies to a manifest
66pub fn add(workspace: &Workspace<'_>, options: &AddOptions<'_>) -> CargoResult<()> {
67    let dep_table = options
68        .section
69        .to_table()
70        .into_iter()
71        .map(String::from)
72        .collect::<Vec<_>>();
73
74    let manifest_path = options.spec.manifest_path().to_path_buf();
75    let mut manifest = LocalManifest::try_new(&manifest_path)?;
76    let original_raw_manifest = manifest.to_string();
77    let legacy = manifest.get_legacy_sections();
78    if !legacy.is_empty() {
79        anyhow::bail!(
80            "Deprecated dependency sections are unsupported: {}",
81            legacy.join(", ")
82        );
83    }
84
85    let mut registry = workspace.package_registry()?;
86
87    let deps = {
88        let _lock = options
89            .gctx
90            .acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
91        registry.lock_patches();
92        options
93            .dependencies
94            .iter()
95            .map(|raw| {
96                resolve_dependency(
97                    &manifest,
98                    raw,
99                    workspace,
100                    &options.spec,
101                    &options.section,
102                    options.honor_rust_version,
103                    options.gctx,
104                    &mut registry,
105                )
106            })
107            .collect::<CargoResult<Vec<_>>>()?
108    };
109
110    let was_sorted = manifest
111        .get_table(&dep_table)
112        .map(TomlItem::as_table)
113        .map_or(true, |table_option| {
114            table_option.map_or(true, |table| {
115                table
116                    .get_values()
117                    .iter_mut()
118                    .map(|(key, _)| {
119                        // get_values key paths always have at least one key.
120                        key.remove(0)
121                    })
122                    .is_sorted()
123            })
124        });
125    for dep in deps {
126        print_action_msg(&mut options.gctx.shell(), &dep, &dep_table)?;
127        if let Some(Source::Path(src)) = dep.source() {
128            if src.path == manifest.path.parent().unwrap_or_else(|| Path::new("")) {
129                anyhow::bail!(
130                    "cannot add `{}` as a dependency to itself",
131                    manifest.package_name()?
132                )
133            }
134        }
135
136        let available_features = dep
137            .available_features
138            .keys()
139            .map(|s| s.as_ref())
140            .collect::<BTreeSet<&str>>();
141        let mut unknown_features: Vec<&str> = Vec::new();
142        if let Some(req_feats) = dep.features.as_ref() {
143            let req_feats: BTreeSet<_> = req_feats.iter().map(|s| s.as_str()).collect();
144            unknown_features.extend(req_feats.difference(&available_features).copied());
145        }
146        if let Some(inherited_features) = dep.inherited_features.as_ref() {
147            let inherited_features: BTreeSet<_> =
148                inherited_features.iter().map(|s| s.as_str()).collect();
149            unknown_features.extend(inherited_features.difference(&available_features).copied());
150        }
151
152        unknown_features.sort();
153
154        if !unknown_features.is_empty() {
155            let (mut activated, mut deactivated) = dep.features();
156            // Since the unknown features have been added to the DependencyUI we need to remove
157            // them to present the "correct" features that can be specified for the crate.
158            deactivated.retain(|f| !unknown_features.contains(f));
159            activated.retain(|f| !unknown_features.contains(f));
160
161            let mut message = format!(
162                "unrecognized feature{} for crate {}: {}\n",
163                if unknown_features.len() == 1 { "" } else { "s" },
164                dep.name,
165                unknown_features.iter().format(", "),
166            );
167            if activated.is_empty() && deactivated.is_empty() {
168                write!(message, "no features available for crate {}", dep.name)?;
169            } else {
170                if !deactivated.is_empty() {
171                    if deactivated.len() <= MAX_FEATURE_PRINTS {
172                        writeln!(
173                            message,
174                            "disabled features:\n    {}",
175                            deactivated
176                                .iter()
177                                .map(|s| s.to_string())
178                                .coalesce(|x, y| if x.len() + y.len() < 78 {
179                                    Ok(format!("{x}, {y}"))
180                                } else {
181                                    Err((x, y))
182                                })
183                                .into_iter()
184                                .format("\n    ")
185                        )?;
186                    } else {
187                        writeln!(message, "{} disabled features available", deactivated.len())?;
188                    }
189                }
190                if !activated.is_empty() {
191                    if deactivated.len() + activated.len() <= MAX_FEATURE_PRINTS {
192                        writeln!(
193                            message,
194                            "enabled features:\n    {}",
195                            activated
196                                .iter()
197                                .map(|s| s.to_string())
198                                .coalesce(|x, y| if x.len() + y.len() < 78 {
199                                    Ok(format!("{x}, {y}"))
200                                } else {
201                                    Err((x, y))
202                                })
203                                .into_iter()
204                                .format("\n    ")
205                        )?;
206                    } else {
207                        writeln!(message, "{} enabled features available", activated.len())?;
208                    }
209                }
210            }
211            anyhow::bail!(message.trim().to_owned());
212        }
213
214        print_dep_table_msg(&mut options.gctx.shell(), &dep)?;
215
216        manifest.insert_into_table(
217            &dep_table,
218            &dep,
219            workspace.gctx(),
220            workspace.root(),
221            options.spec.manifest().unstable_features(),
222        )?;
223        if dep.optional == Some(true) {
224            let is_namespaced_features_supported =
225                check_rust_version_for_optional_dependency(options.spec.rust_version())?;
226            if is_namespaced_features_supported {
227                let dep_key = dep.toml_key();
228                if !manifest.is_explicit_dep_activation(dep_key) {
229                    let table = manifest.get_table_mut(&[String::from("features")])?;
230                    let dep_name = dep.rename.as_deref().unwrap_or(&dep.name);
231                    let new_feature: toml_edit::Value =
232                        [format!("dep:{dep_name}")].iter().collect();
233                    table[dep_key] = toml_edit::value(new_feature);
234                    options
235                        .gctx
236                        .shell()
237                        .status("Adding", format!("feature `{dep_key}`"))?;
238                }
239            }
240        }
241        manifest.gc_dep(dep.toml_key());
242    }
243
244    if was_sorted {
245        if let Some(table) = manifest
246            .get_table_mut(&dep_table)
247            .ok()
248            .and_then(TomlItem::as_table_like_mut)
249        {
250            table.sort_values();
251        }
252    }
253
254    if let Some(locked_flag) = options.gctx.locked_flag() {
255        let new_raw_manifest = manifest.to_string();
256        if original_raw_manifest != new_raw_manifest {
257            anyhow::bail!(
258                "the manifest file {} needs to be updated but {locked_flag} was passed to prevent this",
259                manifest.path.display()
260            );
261        }
262    }
263
264    if options.dry_run {
265        options.gctx.shell().warn("aborting add due to dry run")?;
266    } else {
267        manifest.write()?;
268    }
269
270    Ok(())
271}
272
273/// Dependency entry operation
274#[derive(Clone, Debug, PartialEq, Eq)]
275pub struct DepOp {
276    /// Describes the crate
277    pub crate_spec: Option<String>,
278    /// Dependency key, overriding the package name in `crate_spec`
279    pub rename: Option<String>,
280
281    /// Feature flags to activate
282    pub features: Option<IndexSet<String>>,
283    /// Whether the default feature should be activated
284    pub default_features: Option<bool>,
285
286    /// Whether dependency is optional
287    pub optional: Option<bool>,
288
289    /// Whether dependency is public
290    pub public: Option<bool>,
291
292    /// Registry for looking up dependency version
293    pub registry: Option<String>,
294
295    /// File system path for dependency
296    pub path: Option<String>,
297    /// Specify a named base for a path dependency
298    pub base: Option<String>,
299
300    /// Git repo for dependency
301    pub git: Option<String>,
302    /// Specify an alternative git branch
303    pub branch: Option<String>,
304    /// Specify a specific git rev
305    pub rev: Option<String>,
306    /// Specify a specific git tag
307    pub tag: Option<String>,
308}
309
310fn resolve_dependency(
311    manifest: &LocalManifest,
312    arg: &DepOp,
313    ws: &Workspace<'_>,
314    spec: &Package,
315    section: &DepTable,
316    honor_rust_version: Option<bool>,
317    gctx: &GlobalContext,
318    registry: &mut PackageRegistry<'_>,
319) -> CargoResult<DependencyUI> {
320    let crate_spec = arg
321        .crate_spec
322        .as_deref()
323        .map(CrateSpec::resolve)
324        .transpose()?;
325    let mut selected_dep = if let Some(url) = &arg.git {
326        let mut src = GitSource::new(url);
327        if let Some(branch) = &arg.branch {
328            src = src.set_branch(branch);
329        }
330        if let Some(tag) = &arg.tag {
331            src = src.set_tag(tag);
332        }
333        if let Some(rev) = &arg.rev {
334            src = src.set_rev(rev);
335        }
336
337        let selected = if let Some(crate_spec) = &crate_spec {
338            if let Some(v) = crate_spec.version_req() {
339                // crate specifier includes a version (e.g. `docopt@0.8`)
340                anyhow::bail!("cannot specify a git URL (`{url}`) with a version (`{v}`).");
341            }
342            let dependency = crate_spec.to_dependency()?.set_source(src);
343            let selected = select_package(&dependency, gctx, registry)?;
344            if dependency.name != selected.name {
345                gctx.shell().warn(format!(
346                    "translating `{}` to `{}`",
347                    dependency.name, selected.name,
348                ))?;
349            }
350            selected
351        } else {
352            let mut source = crate::sources::GitSource::new(src.source_id()?, gctx)?;
353            let packages = source.read_packages()?;
354            let package = infer_package_for_git_source(packages, &src)?;
355            Dependency::from(package.summary())
356        };
357        selected
358    } else if let Some(raw_path) = &arg.path {
359        let path = paths::normalize_path(&std::env::current_dir()?.join(raw_path));
360        let mut src = PathSource::new(path);
361        src.base = arg.base.clone();
362
363        if let Some(base) = &arg.base {
364            // Validate that the base is valid.
365            let workspace_root = || Ok(ws.root_manifest().parent().unwrap());
366            lookup_path_base(
367                &PathBaseName::new(base.clone())?,
368                &gctx,
369                &workspace_root,
370                spec.manifest().unstable_features(),
371            )?;
372        }
373
374        let selected = if let Some(crate_spec) = &crate_spec {
375            if let Some(v) = crate_spec.version_req() {
376                // crate specifier includes a version (e.g. `docopt@0.8`)
377                anyhow::bail!("cannot specify a path (`{raw_path}`) with a version (`{v}`).");
378            }
379            let dependency = crate_spec.to_dependency()?.set_source(src);
380            let selected = select_package(&dependency, gctx, registry)?;
381            if dependency.name != selected.name {
382                gctx.shell().warn(format!(
383                    "translating `{}` to `{}`",
384                    dependency.name, selected.name,
385                ))?;
386            }
387            selected
388        } else {
389            let mut source = crate::sources::PathSource::new(&src.path, src.source_id()?, gctx);
390            let package = source.root_package()?;
391            let mut selected = Dependency::from(package.summary());
392            if let Some(Source::Path(selected_src)) = &mut selected.source {
393                selected_src.base = src.base;
394            }
395            selected
396        };
397        selected
398    } else if let Some(crate_spec) = &crate_spec {
399        crate_spec.to_dependency()?
400    } else {
401        anyhow::bail!("dependency name is required");
402    };
403    selected_dep = populate_dependency(selected_dep, arg);
404
405    let lookup = |dep_key: &_| {
406        get_existing_dependency(
407            ws,
408            spec.manifest().unstable_features(),
409            manifest,
410            dep_key,
411            section,
412        )
413    };
414    let old_dep = fuzzy_lookup(&mut selected_dep, lookup, gctx)?;
415    let mut dependency = if let Some(mut old_dep) = old_dep.clone() {
416        if old_dep.name != selected_dep.name {
417            // Assuming most existing keys are not relevant when the package changes
418            if selected_dep.optional.is_none() {
419                selected_dep.optional = old_dep.optional;
420            }
421            selected_dep
422        } else {
423            if selected_dep.source().is_some() {
424                // Overwrite with `crate_spec`
425                old_dep.source = selected_dep.source;
426            }
427            populate_dependency(old_dep, arg)
428        }
429    } else {
430        selected_dep
431    };
432
433    if dependency.source().is_none() {
434        // Checking for a workspace dependency happens first since a member could be specified
435        // in the workspace dependencies table as a dependency
436        let lookup = |toml_key: &_| {
437            Ok(find_workspace_dep(toml_key, ws, ws.root_manifest(), ws.unstable_features()).ok())
438        };
439        if let Some(_dep) = fuzzy_lookup(&mut dependency, lookup, gctx)? {
440            dependency = dependency.set_source(WorkspaceSource::new());
441        } else if let Some(package) = ws.members().find(|p| p.name().as_str() == dependency.name) {
442            // Only special-case workspaces when the user doesn't provide any extra
443            // information, otherwise, trust the user.
444            let mut src = PathSource::new(package.root());
445            // dev-dependencies do not need the version populated
446            if section.kind() != DepKind::Development {
447                let op = "";
448                let v = format!("{op}{version}", version = package.version());
449                src = src.set_version(v);
450            }
451            dependency = dependency.set_source(src);
452        } else {
453            let latest =
454                get_latest_dependency(spec, &dependency, honor_rust_version, gctx, registry)?;
455
456            if dependency.name != latest.name {
457                gctx.shell().warn(format!(
458                    "translating `{}` to `{}`",
459                    dependency.name, latest.name,
460                ))?;
461                dependency.name = latest.name; // Normalize the name
462            }
463            dependency = dependency.set_source(latest.source.expect("latest always has a source"));
464        }
465    }
466
467    if let Some(Source::Workspace(_)) = dependency.source() {
468        check_invalid_ws_keys(dependency.toml_key(), arg)?;
469    }
470
471    let version_required = dependency.source().and_then(|s| s.as_registry()).is_some();
472    let version_optional_in_section = section.kind() == DepKind::Development;
473    let preserve_existing_version = old_dep
474        .as_ref()
475        .map(|d| d.version().is_some())
476        .unwrap_or(false);
477    if !version_required && !preserve_existing_version && version_optional_in_section {
478        // dev-dependencies do not need the version populated
479        dependency = dependency.clear_version();
480    }
481
482    let query = dependency.query(gctx)?;
483    let query = match query {
484        MaybeWorkspace::Workspace(_workspace) => {
485            let dep = find_workspace_dep(
486                dependency.toml_key(),
487                ws,
488                ws.root_manifest(),
489                ws.unstable_features(),
490            )?;
491            if let Some(features) = dep.features.clone() {
492                dependency = dependency.set_inherited_features(features);
493            }
494            let query = dep.query(gctx)?;
495            match query {
496                MaybeWorkspace::Workspace(_) => {
497                    unreachable!("This should have been caught when parsing a workspace root")
498                }
499                MaybeWorkspace::Other(query) => query,
500            }
501        }
502        MaybeWorkspace::Other(query) => query,
503    };
504
505    let dependency = populate_available_features(dependency, &query, registry)?;
506
507    Ok(dependency)
508}
509
510fn fuzzy_lookup(
511    dependency: &mut Dependency,
512    lookup: impl Fn(&str) -> CargoResult<Option<Dependency>>,
513    gctx: &GlobalContext,
514) -> CargoResult<Option<Dependency>> {
515    if let Some(rename) = dependency.rename() {
516        // Manually implement `toml_key` to restrict fuzzy lookups to only package names to mirror `PackageRegistry::query()`
517        return lookup(rename);
518    }
519
520    for name_permutation in [
521        dependency.name.clone(),
522        dependency.name.replace('-', "_"),
523        dependency.name.replace('_', "-"),
524    ] {
525        let Some(dep) = lookup(&name_permutation)? else {
526            continue;
527        };
528
529        if dependency.name != name_permutation {
530            // Mirror the fuzzy matching policy of `PackageRegistry::query()`
531            if !matches!(dep.source, Some(Source::Registry(_))) {
532                continue;
533            }
534            gctx.shell().warn(format!(
535                "translating `{}` to `{}`",
536                dependency.name, &name_permutation,
537            ))?;
538            dependency.name = name_permutation;
539        }
540        return Ok(Some(dep));
541    }
542
543    Ok(None)
544}
545
546/// When { workspace = true } you cannot define other keys that configure
547/// the source of the dependency such as `version`, `registry`, `registry-index`,
548/// `path`, `git`, `branch`, `tag`, `rev`, or `package`. You can also not define
549/// `default-features`.
550///
551/// Only `default-features`, `registry` and `rename` need to be checked
552///  for currently. This is because `git` and its associated keys, `path`, and
553/// `version`  should all bee checked before this is called. `rename` is checked
554/// for as it turns into `package`
555fn check_invalid_ws_keys(toml_key: &str, arg: &DepOp) -> CargoResult<()> {
556    fn err_msg(toml_key: &str, flag: &str, field: &str) -> String {
557        format!(
558            "cannot override workspace dependency with `{flag}`, \
559            either change `workspace.dependencies.{toml_key}.{field}` \
560            or define the dependency exclusively in the package's manifest"
561        )
562    }
563
564    if arg.default_features.is_some() {
565        anyhow::bail!(
566            "{}",
567            err_msg(toml_key, "--default-features", "default-features")
568        )
569    }
570    if arg.registry.is_some() {
571        anyhow::bail!("{}", err_msg(toml_key, "--registry", "registry"))
572    }
573    // rename is `package`
574    if arg.rename.is_some() {
575        anyhow::bail!("{}", err_msg(toml_key, "--rename", "package"))
576    }
577    Ok(())
578}
579
580/// When the `--optional` option is added using `cargo add`, we need to
581/// check the current rust-version. As the `dep:` syntax is only available
582/// starting with Rust 1.60.0
583///
584/// `true` means that the rust-version is None or the rust-version is higher
585/// than the version needed.
586///
587/// Note: Previous versions can only use the implicit feature name.
588fn check_rust_version_for_optional_dependency(
589    rust_version: Option<&RustVersion>,
590) -> CargoResult<bool> {
591    match rust_version {
592        Some(version) => {
593            let syntax_support_version = RustVersion::from_str("1.60.0")?;
594            Ok(&syntax_support_version <= version)
595        }
596        None => Ok(true),
597    }
598}
599
600/// Provide the existing dependency for the target table
601///
602/// If it doesn't exist but exists in another table, let's use that as most likely users
603/// want to use the same version across all tables unless they are renaming.
604fn get_existing_dependency(
605    ws: &Workspace<'_>,
606    unstable_features: &Features,
607    manifest: &LocalManifest,
608    dep_key: &str,
609    section: &DepTable,
610) -> CargoResult<Option<Dependency>> {
611    #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
612    enum Key {
613        Error,
614        Dev,
615        Build,
616        Normal,
617        Existing,
618    }
619
620    let mut possible: Vec<_> = manifest
621        .get_dependency_versions(dep_key, ws, unstable_features)
622        .map(|(path, dep)| {
623            let key = if path == *section {
624                (Key::Existing, true)
625            } else if dep.is_err() {
626                (Key::Error, path.target().is_some())
627            } else {
628                let key = match path.kind() {
629                    DepKind::Normal => Key::Normal,
630                    DepKind::Build => Key::Build,
631                    DepKind::Development => Key::Dev,
632                };
633                (key, path.target().is_some())
634            };
635            (key, dep)
636        })
637        .collect();
638    possible.sort_by_key(|(key, _)| *key);
639    let Some((key, dep)) = possible.pop() else {
640        return Ok(None);
641    };
642    let mut dep = dep?;
643
644    if key.0 != Key::Existing {
645        // When the dep comes from a different section, we only care about the source and not any
646        // of the other fields, like `features`
647        let unrelated = dep;
648        dep = Dependency::new(&unrelated.name);
649        dep.source = unrelated.source.clone();
650        dep.registry = unrelated.registry.clone();
651
652        // dev-dependencies do not need the version populated when path is set though we
653        // should preserve it if the user chose to populate it.
654        let version_required = unrelated.source().and_then(|s| s.as_registry()).is_some();
655        let version_optional_in_section = section.kind() == DepKind::Development;
656        if !version_required && version_optional_in_section {
657            dep = dep.clear_version();
658        }
659    }
660
661    Ok(Some(dep))
662}
663
664fn get_latest_dependency(
665    spec: &Package,
666    dependency: &Dependency,
667    honor_rust_version: Option<bool>,
668    gctx: &GlobalContext,
669    registry: &mut PackageRegistry<'_>,
670) -> CargoResult<Dependency> {
671    let query = dependency.query(gctx)?;
672    match query {
673        MaybeWorkspace::Workspace(_) => {
674            unreachable!("registry dependencies required, found a workspace dependency");
675        }
676        MaybeWorkspace::Other(query) => {
677            let possibilities = loop {
678                match registry.query_vec(&query, QueryKind::Normalized) {
679                    std::task::Poll::Ready(res) => {
680                        break res?;
681                    }
682                    std::task::Poll::Pending => registry.block_until_ready()?,
683                }
684            };
685
686            let mut possibilities: Vec<_> = possibilities
687                .into_iter()
688                .map(|s| s.into_summary())
689                .collect();
690
691            possibilities.sort_by_key(|s| {
692                // Fallback to a pre-release if no official release is available by sorting them as
693                // less.
694                let stable = s.version().pre.is_empty();
695                (stable, s.version().clone())
696            });
697
698            let mut latest = possibilities.last().ok_or_else(|| {
699                anyhow::format_err!(
700                    "the crate `{dependency}` could not be found in registry index."
701                )
702            })?;
703
704            if honor_rust_version.unwrap_or(true) {
705                let (req_msrv, is_msrv) = spec
706                    .rust_version()
707                    .cloned()
708                    .map(|msrv| CargoResult::Ok((msrv.clone().into_partial(), true)))
709                    .unwrap_or_else(|| {
710                        let rustc = gctx.load_global_rustc(None)?;
711
712                        // Remove any pre-release identifiers for easier comparison
713                        let rustc_version = rustc.version.clone().into();
714                        Ok((rustc_version, false))
715                    })?;
716
717                let msrvs = possibilities
718                    .iter()
719                    .map(|s| (s, s.rust_version()))
720                    .collect::<Vec<_>>();
721
722                // Find the latest version of the dep which has a compatible rust-version. To
723                // determine whether or not one rust-version is compatible with another, we
724                // compare the lowest possible versions they could represent, and treat
725                // candidates without a rust-version as compatible by default.
726                let latest_msrv = latest_compatible(&msrvs, &req_msrv).ok_or_else(|| {
727                        let name = spec.name();
728                        let dep_name = &dependency.name;
729                        let latest_version = latest.version();
730                        let latest_msrv = latest
731                            .rust_version()
732                            .expect("as `None` are compatible, we can't be here");
733                        if is_msrv {
734                            anyhow::format_err!(
735                                "\
736no version of crate `{dep_name}` can maintain {name}'s rust-version of {req_msrv}
737help: pass `--ignore-rust-version` to select {dep_name}@{latest_version} which requires rustc {latest_msrv}"
738                            )
739                        } else {
740                            anyhow::format_err!(
741                                "\
742no version of crate `{dep_name}` is compatible with rustc {req_msrv}
743help: pass `--ignore-rust-version` to select {dep_name}@{latest_version} which requires rustc {latest_msrv}"
744                            )
745                        }
746                    })?;
747
748                if latest_msrv.version() < latest.version() {
749                    let latest_version = latest.version();
750                    let latest_rust_version = latest.rust_version().unwrap();
751                    let name = spec.name();
752                    if is_msrv {
753                        gctx.shell().warn(format_args!(
754                            "\
755ignoring {dependency}@{latest_version} (which requires rustc {latest_rust_version}) to maintain {name}'s rust-version of {req_msrv}",
756                        ))?;
757                    } else {
758                        gctx.shell().warn(format_args!(
759                            "\
760ignoring {dependency}@{latest_version} (which requires rustc {latest_rust_version}) as it is incompatible with rustc {req_msrv}",
761                        ))?;
762                    }
763
764                    latest = latest_msrv;
765                }
766            }
767
768            let mut dep = Dependency::from(latest);
769            if let Some(reg_name) = dependency.registry.as_deref() {
770                dep = dep.set_registry(reg_name);
771            }
772            Ok(dep)
773        }
774    }
775}
776
777/// Of MSRV-compatible summaries, find the highest version
778///
779/// Assumptions:
780/// - `msrvs` is sorted by version
781fn latest_compatible<'s>(
782    msrvs: &[(&'s Summary, Option<&RustVersion>)],
783    pkg_msrv: &PartialVersion,
784) -> Option<&'s Summary> {
785    msrvs
786        .iter()
787        .filter(|(_, dep_msrv)| {
788            dep_msrv
789                .as_ref()
790                .map(|dep_msrv| dep_msrv.is_compatible_with(pkg_msrv))
791                .unwrap_or(true)
792        })
793        .map(|(s, _)| s)
794        .last()
795        .copied()
796}
797
798fn select_package(
799    dependency: &Dependency,
800    gctx: &GlobalContext,
801    registry: &mut PackageRegistry<'_>,
802) -> CargoResult<Dependency> {
803    let query = dependency.query(gctx)?;
804    match query {
805        MaybeWorkspace::Workspace(_) => {
806            unreachable!("path or git dependency expected, found workspace dependency");
807        }
808        MaybeWorkspace::Other(query) => {
809            let possibilities = loop {
810                // Exact to avoid returning all for path/git
811                match registry.query_vec(&query, QueryKind::Normalized) {
812                    std::task::Poll::Ready(res) => {
813                        break res?;
814                    }
815                    std::task::Poll::Pending => registry.block_until_ready()?,
816                }
817            };
818
819            let possibilities: Vec<_> = possibilities
820                .into_iter()
821                .map(|s| s.into_summary())
822                .collect();
823
824            match possibilities.len() {
825                0 => {
826                    let source = dependency
827                        .source()
828                        .expect("source should be resolved before here");
829                    anyhow::bail!("the crate `{dependency}` could not be found at `{source}`")
830                }
831                1 => {
832                    let mut dep = Dependency::from(&possibilities[0]);
833                    if let Some(reg_name) = dependency.registry.as_deref() {
834                        dep = dep.set_registry(reg_name);
835                    }
836                    if let Some(Source::Path(PathSource { base, .. })) = dependency.source() {
837                        if let Some(Source::Path(dep_src)) = &mut dep.source {
838                            dep_src.base = base.clone();
839                        }
840                    }
841                    Ok(dep)
842                }
843                _ => {
844                    let source = dependency
845                        .source()
846                        .expect("source should be resolved before here");
847                    anyhow::bail!(
848                        "unexpectedly found multiple copies of crate `{dependency}` at `{source}`"
849                    )
850                }
851            }
852        }
853    }
854}
855
856fn infer_package_for_git_source(
857    mut packages: Vec<Package>,
858    src: &dyn std::fmt::Display,
859) -> CargoResult<Package> {
860    let package = match packages.len() {
861        0 => unreachable!(
862            "this function should only be called with packages from `GitSource::read_packages` \
863            and that call should error for us when there are no packages"
864        ),
865        1 => packages.pop().expect("match ensured element is present"),
866        _ => {
867            let mut names: Vec<_> = packages
868                .iter()
869                .map(|p| p.name().as_str().to_owned())
870                .collect();
871            names.sort_unstable();
872            anyhow::bail!(
873                "multiple packages found at `{src}`:\n    {}\nTo disambiguate, run `cargo add --git {src} <package>`",
874                names
875                    .iter()
876                    .map(|s| s.to_string())
877                    .coalesce(|x, y| if x.len() + y.len() < 78 {
878                        Ok(format!("{x}, {y}"))
879                    } else {
880                        Err((x, y))
881                    })
882                    .into_iter()
883                    .format("\n    "),
884            );
885        }
886    };
887    Ok(package)
888}
889
890fn populate_dependency(mut dependency: Dependency, arg: &DepOp) -> Dependency {
891    if let Some(registry) = &arg.registry {
892        if registry.is_empty() {
893            dependency.registry = None;
894        } else {
895            dependency.registry = Some(registry.to_owned());
896        }
897    }
898    if let Some(value) = arg.optional {
899        if value {
900            dependency.optional = Some(true);
901        } else {
902            dependency.optional = None;
903        }
904    }
905    if let Some(value) = arg.public {
906        if value {
907            dependency.public = Some(true);
908        } else {
909            dependency.public = None;
910        }
911    }
912    if let Some(value) = arg.default_features {
913        if value {
914            dependency.default_features = None;
915        } else {
916            dependency.default_features = Some(false);
917        }
918    }
919    if let Some(value) = arg.features.as_ref() {
920        dependency = dependency.extend_features(value.iter().cloned());
921    }
922
923    if let Some(rename) = &arg.rename {
924        dependency = dependency.set_rename(rename);
925    }
926
927    dependency
928}
929
930/// Track presentation-layer information with the editable representation of a `[dependencies]`
931/// entry (Dependency)
932pub struct DependencyUI {
933    /// Editable representation of a `[depednencies]` entry
934    dep: Dependency,
935    /// The version of the crate that we pulled `available_features` from
936    available_version: Option<semver::Version>,
937    /// The widest set of features compatible with `Dependency`s version requirement
938    available_features: BTreeMap<String, Vec<String>>,
939}
940
941impl DependencyUI {
942    fn new(dep: Dependency) -> Self {
943        Self {
944            dep,
945            available_version: None,
946            available_features: Default::default(),
947        }
948    }
949
950    fn apply_summary(&mut self, summary: &Summary) {
951        self.available_version = Some(summary.version().clone());
952        self.available_features = summary
953            .features()
954            .iter()
955            .map(|(k, v)| {
956                (
957                    k.as_str().to_owned(),
958                    v.iter()
959                        .filter_map(|v| match v {
960                            FeatureValue::Feature(f) => Some(f.as_str().to_owned()),
961                            FeatureValue::Dep { .. } | FeatureValue::DepFeature { .. } => None,
962                        })
963                        .collect::<Vec<_>>(),
964                )
965            })
966            .collect();
967    }
968
969    fn features(&self) -> (IndexSet<&str>, IndexSet<&str>) {
970        let mut activated: IndexSet<_> =
971            self.features.iter().flatten().map(|s| s.as_str()).collect();
972        if self.default_features().unwrap_or(true) {
973            activated.insert("default");
974        }
975        activated.extend(self.inherited_features.iter().flatten().map(|s| s.as_str()));
976        let mut walk: VecDeque<_> = activated.iter().cloned().collect();
977        while let Some(next) = walk.pop_front() {
978            walk.extend(
979                self.available_features
980                    .get(next)
981                    .into_iter()
982                    .flatten()
983                    .map(|s| s.as_str())
984                    .filter(|s| !activated.contains(s)),
985            );
986            activated.extend(
987                self.available_features
988                    .get(next)
989                    .into_iter()
990                    .flatten()
991                    .map(|s| s.as_str()),
992            );
993        }
994        activated.swap_remove("default");
995        activated.sort();
996        let mut deactivated = self
997            .available_features
998            .keys()
999            .filter(|f| !activated.contains(f.as_str()) && *f != "default")
1000            .map(|f| f.as_str())
1001            .collect::<IndexSet<_>>();
1002        deactivated.sort();
1003        (activated, deactivated)
1004    }
1005}
1006
1007impl<'s> From<&'s Summary> for DependencyUI {
1008    fn from(other: &'s Summary) -> Self {
1009        let dep = Dependency::from(other);
1010        let mut dep = Self::new(dep);
1011        dep.apply_summary(other);
1012        dep
1013    }
1014}
1015
1016impl std::fmt::Display for DependencyUI {
1017    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1018        self.dep.fmt(f)
1019    }
1020}
1021
1022impl std::ops::Deref for DependencyUI {
1023    type Target = Dependency;
1024
1025    fn deref(&self) -> &Self::Target {
1026        &self.dep
1027    }
1028}
1029
1030/// Lookup available features
1031fn populate_available_features(
1032    dependency: Dependency,
1033    query: &crate::core::dependency::Dependency,
1034    registry: &mut PackageRegistry<'_>,
1035) -> CargoResult<DependencyUI> {
1036    let mut dependency = DependencyUI::new(dependency);
1037
1038    if !dependency.available_features.is_empty() {
1039        return Ok(dependency);
1040    }
1041
1042    let possibilities = loop {
1043        match registry.query_vec(&query, QueryKind::Normalized) {
1044            std::task::Poll::Ready(res) => {
1045                break res?;
1046            }
1047            std::task::Poll::Pending => registry.block_until_ready()?,
1048        }
1049    };
1050    // Ensure widest feature flag compatibility by picking the earliest version that could show up
1051    // in the lock file for a given version requirement.
1052    let lowest_common_denominator = possibilities
1053        .iter()
1054        .map(|s| s.as_summary())
1055        .min_by_key(|s| {
1056            // Fallback to a pre-release if no official release is available by sorting them as
1057            // more.
1058            let is_pre = !s.version().pre.is_empty();
1059            (is_pre, s.version())
1060        })
1061        .ok_or_else(|| {
1062            anyhow::format_err!("the crate `{dependency}` could not be found in registry index.")
1063        })?;
1064    dependency.apply_summary(&lowest_common_denominator);
1065
1066    Ok(dependency)
1067}
1068
1069fn print_action_msg(shell: &mut Shell, dep: &DependencyUI, section: &[String]) -> CargoResult<()> {
1070    if matches!(shell.verbosity(), crate::core::shell::Verbosity::Quiet) {
1071        return Ok(());
1072    }
1073
1074    let mut message = String::new();
1075    write!(message, "{}", dep.name)?;
1076    match dep.source() {
1077        Some(Source::Registry(src)) => {
1078            if src.version.chars().next().unwrap_or('0').is_ascii_digit() {
1079                write!(message, " v{}", src.version)?;
1080            } else {
1081                write!(message, " {}", src.version)?;
1082            }
1083        }
1084        Some(Source::Path(_)) => {
1085            write!(message, " (local)")?;
1086        }
1087        Some(Source::Git(_)) => {
1088            write!(message, " (git)")?;
1089        }
1090        Some(Source::Workspace(_)) => {
1091            write!(message, " (workspace)")?;
1092        }
1093        None => {}
1094    }
1095    write!(message, " to")?;
1096    if dep.optional().unwrap_or(false) {
1097        write!(message, " optional")?;
1098    }
1099    if dep.public().unwrap_or(false) {
1100        write!(message, " public")?;
1101    }
1102    let section = if section.len() == 1 {
1103        section[0].clone()
1104    } else {
1105        format!("{} for target `{}`", &section[2], &section[1])
1106    };
1107    write!(message, " {section}")?;
1108    shell.status("Adding", message)
1109}
1110
1111fn print_dep_table_msg(shell: &mut Shell, dep: &DependencyUI) -> CargoResult<()> {
1112    if matches!(shell.verbosity(), crate::core::shell::Verbosity::Quiet) {
1113        return Ok(());
1114    }
1115
1116    let stderr = shell.err();
1117    let good = style::GOOD;
1118    let error = style::ERROR;
1119
1120    let (activated, deactivated) = dep.features();
1121    if !activated.is_empty() || !deactivated.is_empty() {
1122        let prefix = format!("{:>13}", " ");
1123        let suffix = format_features_version_suffix(&dep);
1124
1125        writeln!(stderr, "{prefix}Features{suffix}:")?;
1126
1127        let total_activated = activated.len();
1128        let total_deactivated = deactivated.len();
1129
1130        if total_activated <= MAX_FEATURE_PRINTS {
1131            for feat in activated {
1132                writeln!(stderr, "{prefix}{good}+{good:#} {feat}")?;
1133            }
1134        } else {
1135            writeln!(stderr, "{prefix}{total_activated} activated features")?;
1136        }
1137
1138        if total_activated + total_deactivated <= MAX_FEATURE_PRINTS {
1139            for feat in deactivated {
1140                writeln!(stderr, "{prefix}{error}-{error:#} {feat}")?;
1141            }
1142        } else {
1143            writeln!(stderr, "{prefix}{total_deactivated} deactivated features")?;
1144        }
1145    }
1146
1147    Ok(())
1148}
1149
1150fn format_features_version_suffix(dep: &DependencyUI) -> String {
1151    if let Some(version) = &dep.available_version {
1152        let mut version = version.clone();
1153        version.build = Default::default();
1154        let version = version.to_string();
1155        // Avoid displaying the version if it will visually look like the version req that we
1156        // showed earlier
1157        let version_req = dep
1158            .version()
1159            .and_then(|v| semver::VersionReq::parse(v).ok())
1160            .and_then(|v| precise_version(&v));
1161        if version_req.as_deref() != Some(version.as_str()) {
1162            format!(" as of v{version}")
1163        } else {
1164            "".to_owned()
1165        }
1166    } else {
1167        "".to_owned()
1168    }
1169}
1170
1171fn find_workspace_dep(
1172    toml_key: &str,
1173    ws: &Workspace<'_>,
1174    root_manifest: &Path,
1175    unstable_features: &Features,
1176) -> CargoResult<Dependency> {
1177    let manifest = LocalManifest::try_new(root_manifest)?;
1178    let manifest = manifest
1179        .data
1180        .as_item()
1181        .as_table_like()
1182        .context("could not make `manifest.data` into a table")?;
1183    let workspace = manifest
1184        .get("workspace")
1185        .context("could not find `workspace`")?
1186        .as_table_like()
1187        .context("could not make `manifest.data.workspace` into a table")?;
1188    let dependencies = workspace
1189        .get("dependencies")
1190        .context("could not find `dependencies` table in `workspace`")?
1191        .as_table_like()
1192        .context("could not make `dependencies` into a table")?;
1193    let dep_item = dependencies
1194        .get(toml_key)
1195        .with_context(|| format!("could not find {toml_key} in `workspace.dependencies`"))?;
1196    Dependency::from_toml(
1197        ws.gctx(),
1198        ws.root(),
1199        root_manifest.parent().unwrap(),
1200        unstable_features,
1201        toml_key,
1202        dep_item,
1203    )
1204}
1205
1206/// Convert a `semver::VersionReq` into a rendered `semver::Version` if all fields are fully
1207/// specified.
1208fn precise_version(version_req: &semver::VersionReq) -> Option<String> {
1209    version_req
1210        .comparators
1211        .iter()
1212        .filter(|c| {
1213            matches!(
1214                c.op,
1215                // Only ops we can determine a precise version from
1216                semver::Op::Exact
1217                    | semver::Op::GreaterEq
1218                    | semver::Op::LessEq
1219                    | semver::Op::Tilde
1220                    | semver::Op::Caret
1221                    | semver::Op::Wildcard
1222            )
1223        })
1224        .filter_map(|c| {
1225            // Only do it when full precision is specified
1226            c.minor.and_then(|minor| {
1227                c.patch.map(|patch| semver::Version {
1228                    major: c.major,
1229                    minor,
1230                    patch,
1231                    pre: c.pre.clone(),
1232                    build: Default::default(),
1233                })
1234            })
1235        })
1236        .max()
1237        .map(|v| v.to_string())
1238}