Skip to main content

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