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