#
cargo/ops/cargo_add/
mod.rs

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