#
cargo/ops/registry/info/
mod.rs

    1//! Implementation of `cargo info`.
    2
    3use anyhow::bail;
    4use cargo_util_schemas::core::{PackageIdSpec, PartialVersion};
    5
    6use crate::core::registry::PackageRegistry;
    7use crate::core::{Dependency, Package, PackageId, PackageIdSpecQuery, Registry, Workspace};
    8use crate::ops::registry::info::view::pretty_view;
    9use crate::ops::registry::{RegistryOrIndex, RegistrySourceIds, get_source_id_with_package_id};
    10use crate::ops::resolve_ws;
    11use crate::sources::source::QueryKind;
    12use crate::sources::{IndexSummary, SourceConfigMap};
    13use crate::util::cache_lock::CacheLockMode;
    14use crate::util::command_prelude::root_manifest;
    15use crate::{CargoResult, GlobalContext};
    16
    17mod view;
    18
    19pub fn info(
    20    spec: &PackageIdSpec,
    21    gctx: &GlobalContext,
    22    reg_or_index: Option<RegistryOrIndex>,
    23    explicit_registry: bool,
    24) -> CargoResult<()> {
    25    let source_config = SourceConfigMap::new(gctx)?;
    26    let mut registry = PackageRegistry::new_with_source_config(gctx, source_config)?;
    27    // Make sure we get the lock before we download anything.
    28    let _lock = gctx.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
    29    registry.lock_patches();
    30
    31    // If we can find it in workspace, use it as a specific version.
    32    let nearest_manifest_path = root_manifest(None, gctx).ok();
    33    let ws = nearest_manifest_path
    34        .as_ref()
    35        .and_then(|root| Workspace::new(root, gctx).ok());
    36    validate_locked_and_frozen_options(ws.is_some(), gctx)?;
    37    let nearest_package = ws.as_ref().and_then(|ws| {
    38        nearest_manifest_path
    39            .as_ref()
    40            .and_then(|path| ws.members().find(|p| p.manifest_path() == path))
    41    });
    42    let (mut package_id, is_member) = find_pkgid_in_ws(nearest_package, ws.as_ref(), spec);
    43
    44    // If a local package exists and no explicit registry/index was provided,
    45    // prefer the local package over the default registry
    46    let reg_or_index_to_use = if package_id.is_some() && !explicit_registry {
    47        None
    48    } else {
    49        reg_or_index.as_ref()
    50    };
    51
    52    let (use_package_source_id, source_ids) =
    53        get_source_id_with_package_id(gctx, package_id, reg_or_index_to_use)?;
    54    // If we don't use the package's source, we need to query the package ID from the specified registry.
    55    if !use_package_source_id {
    56        package_id = None;
    57    }
    58
    59    let msrv_from_nearest_manifest_path_or_ws =
    60        try_get_msrv_from_nearest_manifest_or_ws(nearest_package, ws.as_ref());
    61    // If the workspace does not have a specific Rust version,
    62    // or if the command is not called within the workspace, then fallback to the global Rust version.
    63    let rustc_version = match msrv_from_nearest_manifest_path_or_ws {
    64        Some(msrv) => msrv,
    65        None => {
    66            let current_rustc = gctx.load_global_rustc(ws.as_ref())?.version;
    67            // Remove any pre-release identifiers for easier comparison.
    68            // Otherwise, the MSRV check will fail if the current Rust version is a nightly or beta version.
    69            semver::Version::new(
    70                current_rustc.major,
    71                current_rustc.minor,
    72                current_rustc.patch,
    73            )
    74            .into()
    75        }
    76    };
    77    // Only suggest cargo tree command when the package is not a workspace member.
    78    // For workspace members, `cargo tree --package <SPEC> --invert` is useless. It only prints itself.
    79    let suggest_cargo_tree_command = package_id.is_some() && !is_member;
    80
    81    let (summaries, normalized_name) = query_summaries(spec, &mut registry, &source_ids)?;
    82    let normalized_spec = match normalized_name {
    83        Some(name) if name != spec.name() => {
    84            let mut normalized_spec = PackageIdSpec::new(name);
    85
    86            if let Some(version) = spec.partial_version().cloned() {
    87                normalized_spec = normalized_spec.with_version(version);
    88            }
    89
    90            if let Some(url) = spec.url().cloned() {
    91                normalized_spec = normalized_spec.with_url(url);
    92            }
    93
    94            if let Some(kind) = spec.kind().cloned() {
    95                normalized_spec = normalized_spec.with_kind(kind);
    96            }
    97
    98            normalized_spec
    99        }
    100        _ => spec.clone(),
    101    };
    102    let package_id = match package_id {
    103        Some(id) => id,
    104        None => find_pkgid_in_summaries(&summaries, &normalized_spec, &rustc_version, &source_ids)?,
    105    };
    106
    107    if package_id.name() != spec.name() {
    108        gctx.shell().warn(format!(
    109            "translating `{}` to `{}`",
    110            spec.name(),
    111            package_id.name(),
    112        ))?;
    113    }
    114
    115    let package = registry.get(&[package_id])?;
    116    let package = package.get_one(package_id)?;
    117    pretty_view(package, &summaries, suggest_cargo_tree_command, gctx)?;
    118
    119    Ok(())
    120}
    121
    122fn find_pkgid_in_ws(
    123    nearest_package: Option<&Package>,
    124    ws: Option<&Workspace<'_>>,
    125    spec: &PackageIdSpec,
    126) -> (Option<PackageId>, bool) {
    127    let Some(ws) = ws else {
    128        return (None, false);
    129    };
    130
    131    if let Some(member) = ws.members().find(|p| spec.matches(p.package_id())) {
    132        return (Some(member.package_id()), true);
    133    }
    134
    135    let Ok((_, resolve)) = resolve_ws(ws, false) else {
    136        return (None, false);
    137    };
    138
    139    if let Some(package_id) = nearest_package
    140        .map(|p| p.package_id())
    141        .into_iter()
    142        .flat_map(|p| resolve.deps(p))
    143        .map(|(p, _)| p)
    144        .filter(|&p| spec.matches(p))
    145        .max_by_key(|&p| p.version())
    146    {
    147        return (Some(package_id), false);
    148    }
    149
    150    if let Some(package_id) = ws
    151        .members()
    152        .map(|p| p.package_id())
    153        .flat_map(|p| resolve.deps(p))
    154        .map(|(p, _)| p)
    155        .filter(|&p| spec.matches(p))
    156        .max_by_key(|&p| p.version())
    157    {
    158        return (Some(package_id), false);
    159    }
    160
    161    if let Some(package_id) = resolve
    162        .iter()
    163        .filter(|&p| spec.matches(p))
    164        .max_by_key(|&p| p.version())
    165    {
    166        return (Some(package_id), false);
    167    }
    168
    169    (None, false)
    170}
    171
    172fn find_pkgid_in_summaries(
    173    summaries: &[IndexSummary],
    174    normalized_spec: &PackageIdSpec,
    175    rustc_version: &PartialVersion,
    176    source_ids: &RegistrySourceIds,
    177) -> CargoResult<PackageId> {
    178    let summary = summaries
    179        .iter()
    180        .filter(|s| normalized_spec.matches(s.package_id()))
    181        .max_by(|s1, s2| {
    182            // Check the MSRV compatibility.
    183            let s1_matches = s1
    184                .as_summary()
    185                .rust_version()
    186                .map(|v| v.is_compatible_with(rustc_version))
    187                .unwrap_or_else(|| false);
    188            let s2_matches = s2
    189                .as_summary()
    190                .rust_version()
    191                .map(|v| v.is_compatible_with(rustc_version))
    192                .unwrap_or_else(|| false);
    193            // MSRV compatible version is preferred.
    194            match (s1_matches, s2_matches) {
    195                (true, false) => std::cmp::Ordering::Greater,
    196                (false, true) => std::cmp::Ordering::Less,
    197                // If both summaries match the current Rust version or neither do, try to
    198                // pick the latest version.
    199                _ => s1.package_id().version().cmp(s2.package_id().version()),
    200            }
    201        });
    202
    203    match summary {
    204        Some(summary) => Ok(summary.package_id()),
    205        None => {
    206            anyhow::bail!(
    207                "could not find `{}` in registry `{}`",
    208                normalized_spec,
    209                source_ids.original.url()
    210            )
    211        }
    212    }
    213}
    214
    215fn query_summaries(
    216    spec: &PackageIdSpec,
    217    registry: &mut PackageRegistry<'_>,
    218    source_ids: &RegistrySourceIds,
    219) -> CargoResult<(Vec<IndexSummary>, Option<String>)> {
    220    // Query without version requirement to get all index summaries.
    221    let dep = Dependency::parse(spec.name(), None, source_ids.original)?;
    222    let results = loop {
    223        // Use normalized crate name lookup for user-provided package names.
    224        match registry.query_vec(&dep, QueryKind::Normalized) {
    225            std::task::Poll::Ready(res) => {
    226                break res?;
    227            }
    228            std::task::Poll::Pending => registry.block_until_ready()?,
    229        }
    230    };
    231
    232    let normalized_name = results.first().map(|s| s.package_id().name().to_string());
    233
    234    Ok((results, normalized_name))
    235}
    236
    237fn validate_locked_and_frozen_options(
    238    in_workspace: bool,
    239    gctx: &GlobalContext,
    240) -> Result<(), anyhow::Error> {
    241    // Only in workspace, we can use --frozen or --locked.
    242    if !in_workspace {
    243        if let Some(locked_flag) = gctx.locked_flag() {
    244            bail!("the option `{locked_flag}` can only be used within a workspace");
    245        }
    246    }
    247    Ok(())
    248}
    249
    250fn try_get_msrv_from_nearest_manifest_or_ws(
    251    nearest_package: Option<&Package>,
    252    ws: Option<&Workspace<'_>>,
    253) -> Option<PartialVersion> {
    254    // Try to get the MSRV from the nearest manifest.
    255    let rust_version = nearest_package.and_then(|p| p.rust_version().map(|v| v.as_partial()));
    256    // If the nearest manifest does not have a specific Rust version, try to get it from the workspace.
    257    rust_version
    258        .or_else(|| ws.and_then(|ws| ws.lowest_rust_version().map(|v| v.as_partial())))
    259        .cloned()
    260}