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