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