1use crate::core::{Dependency, PackageId, SourceId};
2use crate::util::closest_msg;
3use crate::util::interning::InternedString;
4use crate::util::CargoResult;
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!("feature `{feature}` includes `{fv}`, but `{f}` is not an optional dependency\n\
261 A non-optional dependency of the same name is defined; \
262 consider adding `optional = true` to its definition.");
263 }
264 }
265 }
266 Dep { dep_name } => {
267 if !is_any_dep {
268 bail!("feature `{feature}` includes `{fv}`, but `{dep_name}` is not listed as a dependency");
269 }
270 if !is_optional_dep {
271 bail!(
272 "feature `{feature}` includes `{fv}`, but `{dep_name}` 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 DepFeature {
279 dep_name,
280 dep_feature,
281 weak,
282 } => {
283 if dep_feature.contains('/') {
285 bail!("multiple slashes in feature `{fv}` (included by feature `{feature}`) are not allowed");
286 }
287
288 if let Some(stripped_dep) = dep_name.strip_prefix("dep:") {
290 let has_other_dep = explicitly_listed.contains(stripped_dep);
291 let is_optional = dep_map.get(stripped_dep).is_some_and(|&o| o);
292 let extra_help = if *weak || has_other_dep || !is_optional {
293 String::new()
299 } else {
300 format!(
301 "\nIf the intent is to avoid creating an implicit feature \
302 `{stripped_dep}` for an optional dependency, \
303 then consider replacing this with two values:\n \
304 \"dep:{stripped_dep}\", \"{stripped_dep}/{dep_feature}\""
305 )
306 };
307 bail!(
308 "feature `{feature}` includes `{fv}` with both `dep:` and `/`\n\
309 To fix this, remove the `dep:` prefix.{extra_help}"
310 )
311 }
312
313 if !is_any_dep {
315 bail!(MissingDependencyError {
316 feature: *feature,
317 feature_value: (*fv).clone(),
318 dep_name: *dep_name,
319 weak_optional: *weak,
320 })
321 }
322 if *weak && !is_optional_dep {
323 bail!(
324 "feature `{feature}` includes `{fv}` with a `?`, but `{dep_name}` is not an optional dependency\n\
325 A non-optional dependency of the same name is defined; \
326 consider removing the `?` or changing the dependency to be optional"
327 );
328 }
329 }
330 }
331 }
332 }
333
334 let used: HashSet<_> = map
336 .values()
337 .flatten()
338 .filter_map(|fv| match fv {
339 Dep { dep_name } | DepFeature { dep_name, .. } => Some(dep_name),
340 _ => None,
341 })
342 .collect();
343 if let Some((dep, _)) = dep_map
344 .iter()
345 .find(|&(dep, &is_optional)| is_optional && !used.contains(dep))
346 {
347 bail!(
348 "optional dependency `{dep}` is not included in any feature\n\
349 Make sure that `dep:{dep}` is included in one of features in the [features] table."
350 );
351 }
352
353 Ok(map)
354}
355
356#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
358pub enum FeatureValue {
359 Feature(InternedString),
361 Dep { dep_name: InternedString },
363 DepFeature {
365 dep_name: InternedString,
366 dep_feature: InternedString,
367 weak: bool,
371 },
372}
373
374impl FeatureValue {
375 pub fn new(feature: InternedString) -> FeatureValue {
376 match feature.split_once('/') {
377 Some((dep, dep_feat)) => {
378 let dep_name = dep.strip_suffix('?');
379 FeatureValue::DepFeature {
380 dep_name: dep_name.unwrap_or(dep).into(),
381 dep_feature: dep_feat.into(),
382 weak: dep_name.is_some(),
383 }
384 }
385 None => {
386 if let Some(dep_name) = feature.strip_prefix("dep:") {
387 FeatureValue::Dep {
388 dep_name: dep_name.into(),
389 }
390 } else {
391 FeatureValue::Feature(feature)
392 }
393 }
394 }
395 }
396
397 fn explicit_dep_name(&self) -> Option<InternedString> {
399 match self {
400 FeatureValue::Dep { dep_name, .. } => Some(*dep_name),
401 _ => None,
402 }
403 }
404
405 fn feature_or_dep_name(&self) -> InternedString {
406 match self {
407 FeatureValue::Feature(dep_name)
408 | FeatureValue::Dep { dep_name, .. }
409 | FeatureValue::DepFeature { dep_name, .. } => *dep_name,
410 }
411 }
412}
413
414impl fmt::Display for FeatureValue {
415 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
416 use self::FeatureValue::*;
417 match self {
418 Feature(feat) => write!(f, "{feat}"),
419 Dep { dep_name } => write!(f, "dep:{dep_name}"),
420 DepFeature {
421 dep_name,
422 dep_feature,
423 weak,
424 } => {
425 let weak = if *weak { "?" } else { "" };
426 write!(f, "{dep_name}{weak}/{dep_feature}")
427 }
428 }
429 }
430}
431
432pub type FeatureMap = BTreeMap<InternedString, Vec<FeatureValue>>;