1use 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, normalized_name) = query_summaries(spec, &mut registry, &source_ids)?;
82 let normalized_spec = match normalized_name {
83 Some(name) if name != spec.name() => {
84 let mut normalized_spec = PackageIdSpec::new(name);
85
86 if let Some(version) = spec.partial_version().cloned() {
87 normalized_spec = normalized_spec.with_version(version);
88 }
89
90 if let Some(url) = spec.url().cloned() {
91 normalized_spec = normalized_spec.with_url(url);
92 }
93
94 if let Some(kind) = spec.kind().cloned() {
95 normalized_spec = normalized_spec.with_kind(kind);
96 }
97
98 normalized_spec
99 }
100 _ => spec.clone(),
101 };
102 let package_id = match package_id {
103 Some(id) => id,
104 None => find_pkgid_in_summaries(&summaries, &normalized_spec, &rustc_version, &source_ids)?,
105 };
106
107 if package_id.name() != spec.name() {
108 gctx.shell().warn(format!(
109 "translating `{}` to `{}`",
110 spec.name(),
111 package_id.name(),
112 ))?;
113 }
114
115 let package = registry.get(&[package_id])?;
116 let package = package.get_one(package_id)?;
117 pretty_view(package, &summaries, suggest_cargo_tree_command, gctx)?;
118
119 Ok(())
120}
121
122fn find_pkgid_in_ws(
123 nearest_package: Option<&Package>,
124 ws: Option<&Workspace<'_>>,
125 spec: &PackageIdSpec,
126) -> (Option<PackageId>, bool) {
127 let Some(ws) = ws else {
128 return (None, false);
129 };
130
131 if let Some(member) = ws.members().find(|p| spec.matches(p.package_id())) {
132 return (Some(member.package_id()), true);
133 }
134
135 let Ok((_, resolve)) = resolve_ws(ws, false) else {
136 return (None, false);
137 };
138
139 if let Some(package_id) = nearest_package
140 .map(|p| p.package_id())
141 .into_iter()
142 .flat_map(|p| resolve.deps(p))
143 .map(|(p, _)| p)
144 .filter(|&p| spec.matches(p))
145 .max_by_key(|&p| p.version())
146 {
147 return (Some(package_id), false);
148 }
149
150 if let Some(package_id) = ws
151 .members()
152 .map(|p| p.package_id())
153 .flat_map(|p| resolve.deps(p))
154 .map(|(p, _)| p)
155 .filter(|&p| spec.matches(p))
156 .max_by_key(|&p| p.version())
157 {
158 return (Some(package_id), false);
159 }
160
161 if let Some(package_id) = resolve
162 .iter()
163 .filter(|&p| spec.matches(p))
164 .max_by_key(|&p| p.version())
165 {
166 return (Some(package_id), false);
167 }
168
169 (None, false)
170}
171
172fn find_pkgid_in_summaries(
173 summaries: &[IndexSummary],
174 normalized_spec: &PackageIdSpec,
175 rustc_version: &PartialVersion,
176 source_ids: &RegistrySourceIds,
177) -> CargoResult<PackageId> {
178 let summary = summaries
179 .iter()
180 .filter(|s| normalized_spec.matches(s.package_id()))
181 .max_by(|s1, s2| {
182 let s1_matches = s1
184 .as_summary()
185 .rust_version()
186 .map(|v| v.is_compatible_with(rustc_version))
187 .unwrap_or_else(|| false);
188 let s2_matches = s2
189 .as_summary()
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<IndexSummary>, Option<String>)> {
220 let dep = Dependency::parse(spec.name(), None, source_ids.original)?;
222 let results = loop {
223 match registry.query_vec(&dep, QueryKind::Normalized) {
225 std::task::Poll::Ready(res) => {
226 break res?;
227 }
228 std::task::Poll::Pending => registry.block_until_ready()?,
229 }
230 };
231
232 let normalized_name = results.first().map(|s| s.package_id().name().to_string());
233
234 Ok((results, normalized_name))
235}
236
237fn validate_locked_and_frozen_options(
238 in_workspace: bool,
239 gctx: &GlobalContext,
240) -> Result<(), anyhow::Error> {
241 if !in_workspace {
243 if let Some(locked_flag) = gctx.locked_flag() {
244 bail!("the option `{locked_flag}` can only be used within a workspace");
245 }
246 }
247 Ok(())
248}
249
250fn try_get_msrv_from_nearest_manifest_or_ws(
251 nearest_package: Option<&Package>,
252 ws: Option<&Workspace<'_>>,
253) -> Option<PartialVersion> {
254 let rust_version = nearest_package.and_then(|p| p.rust_version().map(|v| v.as_partial()));
256 rust_version
258 .or_else(|| ws.and_then(|ws| ws.lowest_rust_version().map(|v| v.as_partial())))
259 .cloned()
260}