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