cargo/core/
summary.rs

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/// Subset of a `Manifest`. Contains only the most important information about
15/// a package.
16///
17/// Summaries are cloned, and should not be mutated after creation
18#[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/// Indicates the dependency inferred from the `dep` syntax that should exist,
34/// but missing on the resolved dependencies tables.
35#[derive(Debug)]
36pub struct MissingDependencyError {
37    pub dep_name: InternedString,
38    pub feature: InternedString,
39    pub feature_value: FeatureValue,
40    /// Indicates the dependency inferred from the `dep?` syntax that is weak optional
41    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        // ****CAUTION**** If you change anything here that may raise a new
72        // error, be sure to coordinate that change with either the index
73        // schema field or the SummariesCache version.
74        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
181// A check that only compiles if Summary is Sync
182const _: fn() = || {
183    fn is_sync<T: Sync>() {}
184    is_sync::<Summary>();
185};
186
187/// Checks features for errors, bailing out a CargoResult:Err if invalid,
188/// and creates `FeatureValues` for each feature.
189fn build_feature_map(
190    features: &BTreeMap<InternedString, Vec<InternedString>>,
191    dependencies: &[Dependency],
192) -> CargoResult<FeatureMap> {
193    use self::FeatureValue::*;
194    // A map of dependency names to whether there are any that are optional.
195    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; // We are done mutating this variable
200
201    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    // Add implicit features for optional dependencies if they weren't
213    // explicitly listed anywhere.
214    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; // We are done mutating this variable
231
232    // Validate features are listed properly.
233    for (feature, fvs) in &map {
234        FeatureName::new(feature)?;
235        for fv in fvs {
236            // Find data for the referenced dependency...
237            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                    // Early check for some unlikely syntax.
282                    if dep_feature.contains('/') {
283                        bail!("multiple slashes in feature `{fv}` (included by feature `{feature}`) are not allowed");
284                    }
285
286                    // dep: cannot be combined with /
287                    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                            // In this case, the user should just remove dep:.
292                            // Note that "hiding" an optional dependency
293                            // wouldn't work with just a single `dep:foo?/bar`
294                            // because there would not be any way to enable
295                            // `foo`.
296                            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                    // Validation of the feature name will be performed in the resolver.
312                    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    // Make sure every optional dep is mentioned at least once.
333    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/// `FeatureValue` represents the types of dependencies a feature can have.
355#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
356pub enum FeatureValue {
357    /// A feature enabling another feature.
358    Feature(InternedString),
359    /// A feature enabling a dependency with `dep:dep_name` syntax.
360    Dep { dep_name: InternedString },
361    /// A feature enabling a feature on a dependency with `crate_name/feat_name` syntax.
362    DepFeature {
363        dep_name: InternedString,
364        dep_feature: InternedString,
365        /// If `true`, indicates the `?` syntax is used, which means this will
366        /// not automatically enable the dependency unless the dependency is
367        /// activated through some other means.
368        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    /// Returns the name of the dependency if and only if it was explicitly named with the `dep:` syntax.
396    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>>;