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