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