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