1use crate::core::{Dependency, PackageId, SourceId};
2use crate::util::CargoResult;
3use crate::util::closest_msg;
4use crate::util::interning::InternedString;
5use anyhow::bail;
6use cargo_util_schemas::manifest::FeatureName;
7use cargo_util_schemas::manifest::RustVersion;
8use semver::Version;
9use std::collections::{BTreeMap, HashMap, HashSet};
10use std::fmt;
11use std::hash::{Hash, Hasher};
12use std::mem;
13use std::sync::Arc;
14
15#[derive(Debug, Clone)]
20pub struct Summary {
21 inner: Arc<Inner>,
22}
23
24#[derive(Debug, Clone)]
25struct Inner {
26 package_id: PackageId,
27 dependencies: Vec<Dependency>,
28 features: Arc<FeatureMap>,
29 checksum: Option<String>,
30 links: Option<InternedString>,
31 rust_version: Option<RustVersion>,
32 pubtime: Option<jiff::Timestamp>,
33}
34
35#[derive(Debug)]
38pub struct MissingDependencyError {
39 pub dep_name: InternedString,
40 pub feature: InternedString,
41 pub feature_value: FeatureValue,
42 pub weak_optional: bool,
44}
45
46impl std::error::Error for MissingDependencyError {}
47
48impl fmt::Display for MissingDependencyError {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 let Self {
51 dep_name,
52 feature,
53 feature_value: fv,
54 ..
55 } = self;
56
57 write!(
58 f,
59 "feature `{feature}` includes `{fv}`, but `{dep_name}` is not a dependency",
60 )
61 }
62}
63
64impl Summary {
65 #[tracing::instrument(skip_all)]
66 pub fn new(
67 pkg_id: PackageId,
68 dependencies: Vec<Dependency>,
69 features: &BTreeMap<InternedString, Vec<InternedString>>,
70 links: Option<impl Into<InternedString>>,
71 rust_version: Option<RustVersion>,
72 ) -> CargoResult<Summary> {
73 for dep in dependencies.iter() {
77 let dep_name = dep.name_in_toml();
78 if dep.is_optional() && !dep.is_transitive() {
79 bail!(
80 "dev-dependencies are not allowed to be optional: `{}`",
81 dep_name
82 )
83 }
84 }
85 let feature_map = build_feature_map(features, &dependencies)?;
86 Ok(Summary {
87 inner: Arc::new(Inner {
88 package_id: pkg_id,
89 dependencies,
90 features: Arc::new(feature_map),
91 checksum: None,
92 links: links.map(|l| l.into()),
93 rust_version,
94 pubtime: None,
95 }),
96 })
97 }
98
99 pub fn package_id(&self) -> PackageId {
100 self.inner.package_id
101 }
102 pub fn name(&self) -> InternedString {
103 self.package_id().name()
104 }
105 pub fn version(&self) -> &Version {
106 self.package_id().version()
107 }
108 pub fn source_id(&self) -> SourceId {
109 self.package_id().source_id()
110 }
111 pub fn dependencies(&self) -> &[Dependency] {
112 &self.inner.dependencies
113 }
114 pub fn features(&self) -> &FeatureMap {
115 &self.inner.features
116 }
117
118 pub fn checksum(&self) -> Option<&str> {
119 self.inner.checksum.as_deref()
120 }
121 pub fn links(&self) -> Option<InternedString> {
122 self.inner.links
123 }
124
125 pub fn rust_version(&self) -> Option<&RustVersion> {
126 self.inner.rust_version.as_ref()
127 }
128
129 pub fn pubtime(&self) -> Option<jiff::Timestamp> {
130 self.inner.pubtime
131 }
132
133 pub fn override_id(mut self, id: PackageId) -> Summary {
134 Arc::make_mut(&mut self.inner).package_id = id;
135 self
136 }
137
138 pub fn set_checksum(&mut self, cksum: String) {
139 Arc::make_mut(&mut self.inner).checksum = Some(cksum);
140 }
141
142 pub fn set_pubtime(&mut self, pubtime: jiff::Timestamp) {
143 Arc::make_mut(&mut self.inner).pubtime = Some(pubtime);
144 }
145
146 pub fn map_dependencies<F>(self, mut f: F) -> Summary
147 where
148 F: FnMut(Dependency) -> Dependency,
149 {
150 self.try_map_dependencies(|dep| Ok(f(dep))).unwrap()
151 }
152
153 pub fn try_map_dependencies<F>(mut self, f: F) -> CargoResult<Summary>
154 where
155 F: FnMut(Dependency) -> CargoResult<Dependency>,
156 {
157 {
158 let slot = &mut Arc::make_mut(&mut self.inner).dependencies;
159 *slot = mem::take(slot)
160 .into_iter()
161 .map(f)
162 .collect::<CargoResult<_>>()?;
163 }
164 Ok(self)
165 }
166
167 pub fn map_source(self, to_replace: SourceId, replace_with: SourceId) -> Summary {
168 let me = if self.package_id().source_id() == to_replace {
169 let new_id = self.package_id().with_source_id(replace_with);
170 self.override_id(new_id)
171 } else {
172 self
173 };
174 me.map_dependencies(|dep| dep.map_source(to_replace, replace_with))
175 }
176}
177
178impl PartialEq for Summary {
179 fn eq(&self, other: &Summary) -> bool {
180 self.inner.package_id == other.inner.package_id
181 }
182}
183
184impl Eq for Summary {}
185
186impl Hash for Summary {
187 fn hash<H: Hasher>(&self, state: &mut H) {
188 self.inner.package_id.hash(state);
189 }
190}
191
192const _: fn() = || {
194 fn is_sync<T: Sync>() {}
195 is_sync::<Summary>();
196};
197
198fn build_feature_map(
201 features: &BTreeMap<InternedString, Vec<InternedString>>,
202 dependencies: &[Dependency],
203) -> CargoResult<FeatureMap> {
204 use self::FeatureValue::*;
205 let mut dep_map: HashMap<InternedString, bool> = HashMap::new();
207 for dep in dependencies.iter() {
208 *dep_map.entry(dep.name_in_toml()).or_insert(false) |= dep.is_optional();
209 }
210 let dep_map = dep_map; let mut map: FeatureMap = features
213 .iter()
214 .map(|(feature, list)| {
215 let fvs: Vec<_> = list
216 .iter()
217 .map(|feat_value| FeatureValue::new(*feat_value))
218 .collect();
219 (*feature, fvs)
220 })
221 .collect();
222
223 let explicitly_listed: HashSet<_> = map
226 .values()
227 .flatten()
228 .filter_map(|fv| fv.explicit_dep_name())
229 .collect();
230
231 for dep in dependencies {
232 if !dep.is_optional() {
233 continue;
234 }
235 let dep_name = dep.name_in_toml();
236 if features.contains_key(&dep_name) || explicitly_listed.contains(&dep_name) {
237 continue;
238 }
239 map.insert(dep_name, vec![Dep { dep_name }]);
240 }
241 let map = map; for (feature, fvs) in &map {
245 FeatureName::new(feature)?;
246 for fv in fvs {
247 let dep_data = dep_map.get(&fv.feature_or_dep_name());
249 let is_any_dep = dep_data.is_some();
250 let is_optional_dep = dep_data.is_some_and(|&o| o);
251 match fv {
252 Feature(f) => {
253 if !features.contains_key(f) {
254 if !is_any_dep {
255 let closest = closest_msg(f, features.keys(), |k| k, "feature");
256 bail!(
257 "feature `{feature}` includes `{fv}` which is neither a dependency \
258 nor another feature{closest}"
259 );
260 }
261 if is_optional_dep {
262 if !map.contains_key(f) {
263 bail!(
264 "feature `{feature}` includes `{fv}`, but `{f}` is an \
265 optional dependency without an implicit feature\n\
266 Use `dep:{f}` to enable the dependency."
267 );
268 }
269 } else {
270 bail!(
271 "feature `{feature}` includes `{fv}`, but `{f}` is not an optional dependency\n\
272 A non-optional dependency of the same name is defined; \
273 consider adding `optional = true` to its definition."
274 );
275 }
276 }
277 }
278 Dep { dep_name } => {
279 if !is_any_dep {
280 bail!(
281 "feature `{feature}` includes `{fv}`, but `{dep_name}` is not listed as a dependency"
282 );
283 }
284 if !is_optional_dep {
285 bail!(
286 "feature `{feature}` includes `{fv}`, but `{dep_name}` is not an optional dependency\n\
287 A non-optional dependency of the same name is defined; \
288 consider adding `optional = true` to its definition."
289 );
290 }
291 }
292 DepFeature {
293 dep_name,
294 dep_feature,
295 weak,
296 } => {
297 if dep_feature.contains('/') {
299 bail!(
300 "multiple slashes in feature `{fv}` (included by feature `{feature}`) are not allowed"
301 );
302 }
303
304 if let Some(stripped_dep) = dep_name.strip_prefix("dep:") {
306 let has_other_dep = explicitly_listed.contains(stripped_dep);
307 let is_optional = dep_map.get(stripped_dep).is_some_and(|&o| o);
308 let extra_help = if *weak || has_other_dep || !is_optional {
309 String::new()
315 } else {
316 format!(
317 "\nIf the intent is to avoid creating an implicit feature \
318 `{stripped_dep}` for an optional dependency, \
319 then consider replacing this with two values:\n \
320 \"dep:{stripped_dep}\", \"{stripped_dep}/{dep_feature}\""
321 )
322 };
323 bail!(
324 "feature `{feature}` includes `{fv}` with both `dep:` and `/`\n\
325 To fix this, remove the `dep:` prefix.{extra_help}"
326 )
327 }
328
329 if !is_any_dep {
331 bail!(MissingDependencyError {
332 feature: *feature,
333 feature_value: (*fv).clone(),
334 dep_name: *dep_name,
335 weak_optional: *weak,
336 })
337 }
338 if *weak && !is_optional_dep {
339 bail!(
340 "feature `{feature}` includes `{fv}` with a `?`, but `{dep_name}` is not an optional dependency\n\
341 A non-optional dependency of the same name is defined; \
342 consider removing the `?` or changing the dependency to be optional"
343 );
344 }
345 }
346 }
347 }
348 }
349
350 let used: HashSet<_> = map
352 .values()
353 .flatten()
354 .filter_map(|fv| match fv {
355 Dep { dep_name } | DepFeature { dep_name, .. } => Some(dep_name),
356 _ => None,
357 })
358 .collect();
359 if let Some((dep, _)) = dep_map
360 .iter()
361 .find(|&(dep, &is_optional)| is_optional && !used.contains(dep))
362 {
363 bail!(
364 "optional dependency `{dep}` is not included in any feature\n\
365 Make sure that `dep:{dep}` is included in one of features in the [features] table."
366 );
367 }
368
369 Ok(map)
370}
371
372#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
374pub enum FeatureValue {
375 Feature(InternedString),
377 Dep { dep_name: InternedString },
379 DepFeature {
381 dep_name: InternedString,
382 dep_feature: InternedString,
383 weak: bool,
387 },
388}
389
390impl FeatureValue {
391 pub fn new(feature: InternedString) -> FeatureValue {
392 match feature.split_once('/') {
393 Some((dep, dep_feat)) => {
394 let dep_name = dep.strip_suffix('?');
395 FeatureValue::DepFeature {
396 dep_name: dep_name.unwrap_or(dep).into(),
397 dep_feature: dep_feat.into(),
398 weak: dep_name.is_some(),
399 }
400 }
401 None => {
402 if let Some(dep_name) = feature.strip_prefix("dep:") {
403 FeatureValue::Dep {
404 dep_name: dep_name.into(),
405 }
406 } else {
407 FeatureValue::Feature(feature)
408 }
409 }
410 }
411 }
412
413 fn explicit_dep_name(&self) -> Option<InternedString> {
415 match self {
416 FeatureValue::Dep { dep_name, .. } => Some(*dep_name),
417 _ => None,
418 }
419 }
420
421 fn feature_or_dep_name(&self) -> InternedString {
422 match self {
423 FeatureValue::Feature(dep_name)
424 | FeatureValue::Dep { dep_name, .. }
425 | FeatureValue::DepFeature { dep_name, .. } => *dep_name,
426 }
427 }
428}
429
430impl fmt::Display for FeatureValue {
431 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
432 use self::FeatureValue::*;
433 match self {
434 Feature(feat) => write!(f, "{feat}"),
435 Dep { dep_name } => write!(f, "dep:{dep_name}"),
436 DepFeature {
437 dep_name,
438 dep_feature,
439 weak,
440 } => {
441 let weak = if *weak { "?" } else { "" };
442 write!(f, "{dep_name}{weak}/{dep_feature}")
443 }
444 }
445 }
446}
447
448pub type FeatureMap = BTreeMap<InternedString, Vec<FeatureValue>>;