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