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 = query_summaries(spec, &mut registry, &source_ids)?;
82    let package_id = match package_id {
83        Some(id) => id,
84        None => find_pkgid_in_summaries(&summaries, spec, &rustc_version, &source_ids)?,
85    };
86
87    let package = registry.get(&[package_id])?;
88    let package = package.get_one(package_id)?;
89    pretty_view(package, &summaries, suggest_cargo_tree_command, gctx)?;
90
91    Ok(())
92}
93
94fn find_pkgid_in_ws(
95    nearest_package: Option<&Package>,
96    ws: Option<&Workspace<'_>>,
97    spec: &PackageIdSpec,
98) -> (Option<PackageId>, bool) {
99    let Some(ws) = ws else {
100        return (None, false);
101    };
102
103    if let Some(member) = ws.members().find(|p| spec.matches(p.package_id())) {
104        return (Some(member.package_id()), true);
105    }
106
107    let Ok((_, resolve)) = resolve_ws(ws, false) else {
108        return (None, false);
109    };
110
111    if let Some(package_id) = nearest_package
112        .map(|p| p.package_id())
113        .into_iter()
114        .flat_map(|p| resolve.deps(p))
115        .map(|(p, _)| p)
116        .filter(|&p| spec.matches(p))
117        .max_by_key(|&p| p.version())
118    {
119        return (Some(package_id), false);
120    }
121
122    if let Some(package_id) = ws
123        .members()
124        .map(|p| p.package_id())
125        .flat_map(|p| resolve.deps(p))
126        .map(|(p, _)| p)
127        .filter(|&p| spec.matches(p))
128        .max_by_key(|&p| p.version())
129    {
130        return (Some(package_id), false);
131    }
132
133    if let Some(package_id) = resolve
134        .iter()
135        .filter(|&p| spec.matches(p))
136        .max_by_key(|&p| p.version())
137    {
138        return (Some(package_id), false);
139    }
140
141    (None, false)
142}
143
144fn find_pkgid_in_summaries(
145    summaries: &[IndexSummary],
146    spec: &PackageIdSpec,
147    rustc_version: &PartialVersion,
148    source_ids: &RegistrySourceIds,
149) -> CargoResult<PackageId> {
150    let summary = summaries
151        .iter()
152        .filter(|s| spec.matches(s.package_id()))
153        .max_by(|s1, s2| {
154            // Check the MSRV compatibility.
155            let s1_matches = s1
156                .as_summary()
157                .rust_version()
158                .map(|v| v.is_compatible_with(rustc_version))
159                .unwrap_or_else(|| false);
160            let s2_matches = s2
161                .as_summary()
162                .rust_version()
163                .map(|v| v.is_compatible_with(rustc_version))
164                .unwrap_or_else(|| false);
165            // MSRV compatible version is preferred.
166            match (s1_matches, s2_matches) {
167                (true, false) => std::cmp::Ordering::Greater,
168                (false, true) => std::cmp::Ordering::Less,
169                // If both summaries match the current Rust version or neither do, try to
170                // pick the latest version.
171                _ => s1.package_id().version().cmp(s2.package_id().version()),
172            }
173        });
174
175    match summary {
176        Some(summary) => Ok(summary.package_id()),
177        None => {
178            anyhow::bail!(
179                "could not find `{}` in registry `{}`",
180                spec,
181                source_ids.original.url()
182            )
183        }
184    }
185}
186
187fn query_summaries(
188    spec: &PackageIdSpec,
189    registry: &mut PackageRegistry<'_>,
190    source_ids: &RegistrySourceIds,
191) -> CargoResult<Vec<IndexSummary>> {
192    // Query without version requirement to get all index summaries.
193    let dep = Dependency::parse(spec.name(), None, source_ids.original)?;
194    loop {
195        // Exact to avoid returning all for path/git
196        match registry.query_vec(&dep, QueryKind::Exact) {
197            std::task::Poll::Ready(res) => {
198                break res;
199            }
200            std::task::Poll::Pending => registry.block_until_ready()?,
201        }
202    }
203}
204
205fn validate_locked_and_frozen_options(
206    in_workspace: bool,
207    gctx: &GlobalContext,
208) -> Result<(), anyhow::Error> {
209    // Only in workspace, we can use --frozen or --locked.
210    if !in_workspace {
211        if let Some(locked_flag) = gctx.locked_flag() {
212            bail!("the option `{locked_flag}` can only be used within a workspace");
213        }
214    }
215    Ok(())
216}
217
218fn try_get_msrv_from_nearest_manifest_or_ws(
219    nearest_package: Option<&Package>,
220    ws: Option<&Workspace<'_>>,
221) -> Option<PartialVersion> {
222    // Try to get the MSRV from the nearest manifest.
223    let rust_version = nearest_package.and_then(|p| p.rust_version().map(|v| v.as_partial()));
224    // If the nearest manifest does not have a specific Rust version, try to get it from the workspace.
225    rust_version
226        .or_else(|| ws.and_then(|ws| ws.lowest_rust_version().map(|v| v.as_partial())))
227        .cloned()
228}