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