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