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