cargo/core/
summary.rs

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/// Subset of a `Manifest`. Contains only the most important information about
16/// a package.
17///
18/// Summaries are cloned, and should not be mutated after creation
19#[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/// Indicates the dependency inferred from the `dep` syntax that should exist,
36/// but missing on the resolved dependencies tables.
37#[derive(Debug)]
38pub struct MissingDependencyError {
39    pub dep_name: InternedString,
40    pub feature: InternedString,
41    pub feature_value: FeatureValue,
42    /// Indicates the dependency inferred from the `dep?` syntax that is weak optional
43    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        // ****CAUTION**** If you change anything here that may raise a new
74        // error, be sure to coordinate that change with either the index
75        // schema field or the SummariesCache version.
76        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
192// A check that only compiles if Summary is Sync
193const _: fn() = || {
194    fn is_sync<T: Sync>() {}
195    is_sync::<Summary>();
196};
197
198/// Checks features for errors, bailing out a CargoResult:Err if invalid,
199/// and creates `FeatureValues` for each feature.
200fn build_feature_map(
201    features: &BTreeMap<InternedString, Vec<InternedString>>,
202    dependencies: &[Dependency],
203) -> CargoResult<FeatureMap> {
204    use self::FeatureValue::*;
205    // A map of dependency names to whether there are any that are optional.
206    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; // We are done mutating this variable
211
212    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    // Add implicit features for optional dependencies if they weren't
224    // explicitly listed anywhere.
225    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; // We are done mutating this variable
242
243    // Validate features are listed properly.
244    for (feature, fvs) in &map {
245        FeatureName::new(feature)?;
246        for fv in fvs {
247            // Find data for the referenced dependency...
248            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                    // Early check for some unlikely syntax.
298                    if dep_feature.contains('/') {
299                        bail!(
300                            "multiple slashes in feature `{fv}` (included by feature `{feature}`) are not allowed"
301                        );
302                    }
303
304                    // dep: cannot be combined with /
305                    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                            // In this case, the user should just remove dep:.
310                            // Note that "hiding" an optional dependency
311                            // wouldn't work with just a single `dep:foo?/bar`
312                            // because there would not be any way to enable
313                            // `foo`.
314                            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                    // Validation of the feature name will be performed in the resolver.
330                    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    // Make sure every optional dep is mentioned at least once.
351    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/// `FeatureValue` represents the types of dependencies a feature can have.
373#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
374pub enum FeatureValue {
375    /// A feature enabling another feature.
376    Feature(InternedString),
377    /// A feature enabling a dependency with `dep:dep_name` syntax.
378    Dep { dep_name: InternedString },
379    /// A feature enabling a feature on a dependency with `crate_name/feat_name` syntax.
380    DepFeature {
381        dep_name: InternedString,
382        dep_feature: InternedString,
383        /// If `true`, indicates the `?` syntax is used, which means this will
384        /// not automatically enable the dependency unless the dependency is
385        /// activated through some other means.
386        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    /// Returns the name of the dependency if and only if it was explicitly named with the `dep:` syntax.
414    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>>;