#
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) -> CargoResult<()> {
    24    let source_config = SourceConfigMap::new(gctx)?;
    25    let mut registry = PackageRegistry::new_with_source_config(gctx, source_config)?;
    26    // Make sure we get the lock before we download anything.
    27    let _lock = gctx.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
    28    registry.lock_patches();
    29
    30    // If we can find it in workspace, use it as a specific version.
    31    let nearest_manifest_path = root_manifest(None, gctx).ok();
    32    let ws = nearest_manifest_path
    33        .as_ref()
    34        .and_then(|root| Workspace::new(root, gctx).ok());
    35    validate_locked_and_frozen_options(ws.is_some(), gctx)?;
    36    let nearest_package = ws.as_ref().and_then(|ws| {
    37        nearest_manifest_path
    38            .as_ref()
    39            .and_then(|path| ws.members().find(|p| p.manifest_path() == path))
    40    });
    41    let (mut package_id, is_member) = find_pkgid_in_ws(nearest_package, ws.as_ref(), spec);
    42    let (use_package_source_id, source_ids) =
    43        get_source_id_with_package_id(gctx, package_id, reg_or_index.as_ref())?;
    44    // If we don't use the package's source, we need to query the package ID from the specified registry.
    45    if !use_package_source_id {
    46        package_id = None;
    47    }
    48
    49    let msrv_from_nearest_manifest_path_or_ws =
    50        try_get_msrv_from_nearest_manifest_or_ws(nearest_package, ws.as_ref());
    51    // If the workspace does not have a specific Rust version,
    52    // or if the command is not called within the workspace, then fallback to the global Rust version.
    53    let rustc_version = match msrv_from_nearest_manifest_path_or_ws {
    54        Some(msrv) => msrv,
    55        None => {
    56            let current_rustc = gctx.load_global_rustc(ws.as_ref())?.version;
    57            // Remove any pre-release identifiers for easier comparison.
    58            // Otherwise, the MSRV check will fail if the current Rust version is a nightly or beta version.
    59            semver::Version::new(
    60                current_rustc.major,
    61                current_rustc.minor,
    62                current_rustc.patch,
    63            )
    64            .into()
    65        }
    66    };
    67    // Only suggest cargo tree command when the package is not a workspace member.
    68    // For workspace members, `cargo tree --package <SPEC> --invert` is useless. It only prints itself.
    69    let suggest_cargo_tree_command = package_id.is_some() && !is_member;
    70
    71    let summaries = query_summaries(spec, &mut registry, &source_ids)?;
    72    let package_id = match package_id {
    73        Some(id) => id,
    74        None => find_pkgid_in_summaries(&summaries, spec, &rustc_version, &source_ids)?,
    75    };
    76
    77    let package = registry.get(&[package_id])?;
    78    let package = package.get_one(package_id)?;
    79    pretty_view(package, &summaries, suggest_cargo_tree_command, gctx)?;
    80
    81    Ok(())
    82}
    83
    84fn find_pkgid_in_ws(
    85    nearest_package: Option<&Package>,
    86    ws: Option<&Workspace<'_>>,
    87    spec: &PackageIdSpec,
    88) -> (Option<PackageId>, bool) {
    89    let Some(ws) = ws else {
    90        return (None, false);
    91    };
    92
    93    if let Some(member) = ws.members().find(|p| spec.matches(p.package_id())) {
    94        return (Some(member.package_id()), true);
    95    }
    96
    97    let Ok((_, resolve)) = resolve_ws(ws, false) else {
    98        return (None, false);
    99    };
    100
    101    if let Some(package_id) = nearest_package
    102        .map(|p| p.package_id())
    103        .into_iter()
    104        .flat_map(|p| resolve.deps(p))
    105        .map(|(p, _)| p)
    106        .filter(|&p| spec.matches(p))
    107        .max_by_key(|&p| p.version())
    108    {
    109        return (Some(package_id), false);
    110    }
    111
    112    if let Some(package_id) = ws
    113        .members()
    114        .map(|p| p.package_id())
    115        .flat_map(|p| resolve.deps(p))
    116        .map(|(p, _)| p)
    117        .filter(|&p| spec.matches(p))
    118        .max_by_key(|&p| p.version())
    119    {
    120        return (Some(package_id), false);
    121    }
    122
    123    if let Some(package_id) = resolve
    124        .iter()
    125        .filter(|&p| spec.matches(p))
    126        .max_by_key(|&p| p.version())
    127    {
    128        return (Some(package_id), false);
    129    }
    130
    131    (None, false)
    132}
    133
    134fn find_pkgid_in_summaries(
    135    summaries: &[IndexSummary],
    136    spec: &PackageIdSpec,
    137    rustc_version: &PartialVersion,
    138    source_ids: &RegistrySourceIds,
    139) -> CargoResult<PackageId> {
    140    let summary = summaries
    141        .iter()
    142        .filter(|s| spec.matches(s.package_id()))
    143        .max_by(|s1, s2| {
    144            // Check the MSRV compatibility.
    145            let s1_matches = s1
    146                .as_summary()
    147                .rust_version()
    148                .map(|v| v.is_compatible_with(rustc_version))
    149                .unwrap_or_else(|| false);
    150            let s2_matches = s2
    151                .as_summary()
    152                .rust_version()
    153                .map(|v| v.is_compatible_with(rustc_version))
    154                .unwrap_or_else(|| false);
    155            // MSRV compatible version is preferred.
    156            match (s1_matches, s2_matches) {
    157                (true, false) => std::cmp::Ordering::Greater,
    158                (false, true) => std::cmp::Ordering::Less,
    159                // If both summaries match the current Rust version or neither do, try to
    160                // pick the latest version.
    161                _ => s1.package_id().version().cmp(s2.package_id().version()),
    162            }
    163        });
    164
    165    match summary {
    166        Some(summary) => Ok(summary.package_id()),
    167        None => {
    168            anyhow::bail!(
    169                "could not find `{}` in registry `{}`",
    170                spec,
    171                source_ids.original.url()
    172            )
    173        }
    174    }
    175}
    176
    177fn query_summaries(
    178    spec: &PackageIdSpec,
    179    registry: &mut PackageRegistry<'_>,
    180    source_ids: &RegistrySourceIds,
    181) -> CargoResult<Vec<IndexSummary>> {
    182    // Query without version requirement to get all index summaries.
    183    let dep = Dependency::parse(spec.name(), None, source_ids.original)?;
    184    loop {
    185        // Exact to avoid returning all for path/git
    186        match registry.query_vec(&dep, QueryKind::Exact) {
    187            std::task::Poll::Ready(res) => {
    188                break res;
    189            }
    190            std::task::Poll::Pending => registry.block_until_ready()?,
    191        }
    192    }
    193}
    194
    195fn validate_locked_and_frozen_options(
    196    in_workspace: bool,
    197    gctx: &GlobalContext,
    198) -> Result<(), anyhow::Error> {
    199    // Only in workspace, we can use --frozen or --locked.
    200    if !in_workspace {
    201        if let Some(locked_flag) = gctx.locked_flag() {
    202            bail!("the option `{locked_flag}` can only be used within a workspace");
    203        }
    204    }
    205    Ok(())
    206}
    207
    208fn try_get_msrv_from_nearest_manifest_or_ws(
    209    nearest_package: Option<&Package>,
    210    ws: Option<&Workspace<'_>>,
    211) -> Option<PartialVersion> {
    212    // Try to get the MSRV from the nearest manifest.
    213    let rust_version = nearest_package.and_then(|p| p.rust_version().map(|v| v.as_partial()));
    214    // If the nearest manifest does not have a specific Rust version, try to get it from the workspace.
    215    rust_version
    216        .or_else(|| ws.and_then(|ws| ws.lowest_rust_version().map(|v| v.as_partial())))
    217        .cloned()
    218}