1use anyhow::bail;
4use cargo_util_schemas::core::{PackageIdSpec, PartialVersion};
5
6use crate::core::Summary;
7use crate::core::registry::PackageRegistry;
8use crate::core::{Dependency, Package, PackageId, PackageIdSpecQuery, Registry, Workspace};
9use crate::ops::registry::info::view::pretty_view;
10use crate::ops::registry::{RegistryOrIndex, RegistrySourceIds, get_source_id_with_package_id};
11use crate::ops::resolve_ws;
12use crate::sources::IndexSummary;
13use crate::sources::SourceConfigMap;
14use crate::sources::source::QueryKind;
15use crate::util::cache_lock::CacheLockMode;
16use crate::util::command_prelude::root_manifest;
17use crate::{CargoResult, GlobalContext};
18
19mod view;
20
21pub fn info(
22 spec: &PackageIdSpec,
23 gctx: &GlobalContext,
24 reg_or_index: Option<RegistryOrIndex>,
25 explicit_registry: bool,
26) -> CargoResult<()> {
27 let source_config = SourceConfigMap::new(gctx)?;
28 let mut registry = PackageRegistry::new_with_source_config(gctx, source_config)?;
29 let _lock = gctx.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
31 registry.lock_patches();
32
33 let nearest_manifest_path = root_manifest(None, gctx).ok();
35 let ws = nearest_manifest_path
36 .as_ref()
37 .and_then(|root| Workspace::new(root, gctx).ok());
38 validate_locked_and_frozen_options(ws.is_some(), gctx)?;
39 let nearest_package = ws.as_ref().and_then(|ws| {
40 nearest_manifest_path
41 .as_ref()
42 .and_then(|path| ws.members().find(|p| p.manifest_path() == path))
43 });
44 let (mut package_id, is_member) = find_pkgid_in_ws(nearest_package, ws.as_ref(), spec);
45
46 let reg_or_index_to_use = if package_id.is_some() && !explicit_registry {
49 None
50 } else {
51 reg_or_index.as_ref()
52 };
53
54 let (use_package_source_id, source_ids) =
55 get_source_id_with_package_id(gctx, package_id, reg_or_index_to_use)?;
56 if !use_package_source_id {
58 package_id = None;
59 }
60
61 let msrv_from_nearest_manifest_path_or_ws =
62 try_get_msrv_from_nearest_manifest_or_ws(nearest_package, ws.as_ref());
63 let rustc_version = match msrv_from_nearest_manifest_path_or_ws {
66 Some(msrv) => msrv,
67 None => {
68 let current_rustc = gctx.load_global_rustc(ws.as_ref())?.version;
69 semver::Version::new(
72 current_rustc.major,
73 current_rustc.minor,
74 current_rustc.patch,
75 )
76 .into()
77 }
78 };
79 let suggest_cargo_tree_command = package_id.is_some() && !is_member;
82
83 let (summaries, normalized_name) = query_summaries(spec, &mut registry, &source_ids)?;
84 let normalized_spec = match normalized_name {
85 Some(name) if name != spec.name() => {
86 let mut normalized_spec = PackageIdSpec::new(name);
87
88 if let Some(version) = spec.partial_version().cloned() {
89 normalized_spec = normalized_spec.with_version(version);
90 }
91
92 if let Some(url) = spec.url().cloned() {
93 normalized_spec = normalized_spec.with_url(url);
94 }
95
96 if let Some(kind) = spec.kind().cloned() {
97 normalized_spec = normalized_spec.with_kind(kind);
98 }
99
100 normalized_spec
101 }
102 _ => spec.clone(),
103 };
104 let package_id = match package_id {
105 Some(id) => id,
106 None => find_pkgid_in_summaries(&summaries, &normalized_spec, &rustc_version, &source_ids)?,
107 };
108
109 if package_id.name() != spec.name() {
110 gctx.shell().warn(format!(
111 "translating `{}` to `{}`",
112 spec.name(),
113 package_id.name(),
114 ))?;
115 }
116
117 let package = registry.get(&[package_id])?;
118 let package = package.get_one(package_id)?;
119 pretty_view(package, &summaries, suggest_cargo_tree_command, gctx)?;
120
121 Ok(())
122}
123
124fn find_pkgid_in_ws(
125 nearest_package: Option<&Package>,
126 ws: Option<&Workspace<'_>>,
127 spec: &PackageIdSpec,
128) -> (Option<PackageId>, bool) {
129 let Some(ws) = ws else {
130 return (None, false);
131 };
132
133 if let Some(member) = ws.members().find(|p| spec.matches(p.package_id())) {
134 return (Some(member.package_id()), true);
135 }
136
137 let Ok((_, resolve)) = resolve_ws(ws, false) else {
138 return (None, false);
139 };
140
141 if let Some(package_id) = nearest_package
142 .map(|p| p.package_id())
143 .into_iter()
144 .flat_map(|p| resolve.deps(p))
145 .map(|(p, _)| p)
146 .filter(|&p| spec.matches(p))
147 .max_by_key(|&p| p.version())
148 {
149 return (Some(package_id), false);
150 }
151
152 if let Some(package_id) = ws
153 .members()
154 .map(|p| p.package_id())
155 .flat_map(|p| resolve.deps(p))
156 .map(|(p, _)| p)
157 .filter(|&p| spec.matches(p))
158 .max_by_key(|&p| p.version())
159 {
160 return (Some(package_id), false);
161 }
162
163 if let Some(package_id) = resolve
164 .iter()
165 .filter(|&p| spec.matches(p))
166 .max_by_key(|&p| p.version())
167 {
168 return (Some(package_id), false);
169 }
170
171 (None, false)
172}
173
174fn find_pkgid_in_summaries(
175 summaries: &[Summary],
176 normalized_spec: &PackageIdSpec,
177 rustc_version: &PartialVersion,
178 source_ids: &RegistrySourceIds,
179) -> CargoResult<PackageId> {
180 let summary = summaries
181 .iter()
182 .filter(|s| normalized_spec.matches(s.package_id()))
183 .max_by(|s1, s2| {
184 let s1_matches = s1
186 .rust_version()
187 .map(|v| v.is_compatible_with(rustc_version))
188 .unwrap_or_else(|| false);
189 let s2_matches = s2
190 .rust_version()
191 .map(|v| v.is_compatible_with(rustc_version))
192 .unwrap_or_else(|| false);
193 match (s1_matches, s2_matches) {
195 (true, false) => std::cmp::Ordering::Greater,
196 (false, true) => std::cmp::Ordering::Less,
197 _ => 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<Summary>, Option<String>)> {
220 let dep = Dependency::parse(spec.name(), None, source_ids.original)?;
222 let results: Vec<_> = crate::util::block_on(registry.query_vec(&dep, QueryKind::Normalized))?
224 .into_iter()
225 .filter_map(|s| match s {
226 IndexSummary::Candidate(s) => Some(s),
227 _ => None,
228 })
229 .collect();
230
231 let normalized_name = results.first().map(|s| s.package_id().name().to_string());
232
233 Ok((results, normalized_name))
234}
235
236fn validate_locked_and_frozen_options(
237 in_workspace: bool,
238 gctx: &GlobalContext,
239) -> Result<(), anyhow::Error> {
240 if !in_workspace {
242 if let Some(locked_flag) = gctx.locked_flag() {
243 bail!("the option `{locked_flag}` can only be used within a workspace");
244 }
245 }
246 Ok(())
247}
248
249fn try_get_msrv_from_nearest_manifest_or_ws(
250 nearest_package: Option<&Package>,
251 ws: Option<&Workspace<'_>>,
252) -> Option<PartialVersion> {
253 let rust_version = nearest_package.and_then(|p| p.rust_version().map(|v| v.to_partial()));
255 rust_version.or_else(|| ws.and_then(|ws| ws.lowest_rust_version().map(|v| v.to_partial())))
257}