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::{get_source_id_with_package_id, RegistryOrIndex, RegistrySourceIds};
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}