cargo/ops/registry/info/
mod.rs1use 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 let _lock = gctx.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
29 registry.lock_patches();
30
31 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 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 !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 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 semver::Version::new(
70 current_rustc.major,
71 current_rustc.minor,
72 current_rustc.patch,
73 )
74 .into()
75 }
76 };
77 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 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 match (s1_matches, s2_matches) {
167 (true, false) => std::cmp::Ordering::Greater,
168 (false, true) => std::cmp::Ordering::Less,
169 _ => 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 let dep = Dependency::parse(spec.name(), None, source_ids.original)?;
194 loop {
195 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 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 let rust_version = nearest_package.and_then(|p| p.rust_version().map(|v| v.as_partial()));
224 rust_version
226 .or_else(|| ws.and_then(|ws| ws.lowest_rust_version().map(|v| v.as_partial())))
227 .cloned()
228}