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}
33
34/// Indicates the dependency inferred from the `dep` syntax that should exist,
35/// but missing on the resolved dependencies tables.
36#[derive(Debug)]
37pub struct MissingDependencyError {
38    pub dep_name: InternedString,
39    pub feature: InternedString,
40    pub feature_value: FeatureValue,
41    /// Indicates the dependency inferred from the `dep?` syntax that is weak optional
42    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        // ****CAUTION**** If you change anything here that may raise a new
73        // error, be sure to coordinate that change with either the index
74        // schema field or the SummariesCache version.
75        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
182// A check that only compiles if Summary is Sync
183const _: fn() = || {
184    fn is_sync<T: Sync>() {}
185    is_sync::<Summary>();
186};
187
188/// Checks features for errors, bailing out a CargoResult:Err if invalid,
189/// and creates `FeatureValues` for each feature.
190fn build_feature_map(
191    features: &BTreeMap<InternedString, Vec<InternedString>>,
192    dependencies: &[Dependency],
193) -> CargoResult<FeatureMap> {
194    use self::FeatureValue::*;
195    // A map of dependency names to whether there are any that are optional.
196    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; // We are done mutating this variable
201
202    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    // Add implicit features for optional dependencies if they weren't
214    // explicitly listed anywhere.
215    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; // We are done mutating this variable
232
233    // Validate features are listed properly.
234    for (feature, fvs) in &map {
235        FeatureName::new(feature)?;
236        for fv in fvs {
237            // Find data for the referenced dependency...
238            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                    // Early check for some unlikely syntax.
288                    if dep_feature.contains('/') {
289                        bail!(
290                            "multiple slashes in feature `{fv}` (included by feature `{feature}`) are not allowed"
291                        );
292                    }
293
294                    // dep: cannot be combined with /
295                    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                            // In this case, the user should just remove dep:.
300                            // Note that "hiding" an optional dependency
301                            // wouldn't work with just a single `dep:foo?/bar`
302                            // because there would not be any way to enable
303                            // `foo`.
304                            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                    // Validation of the feature name will be performed in the resolver.
320                    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    // Make sure every optional dep is mentioned at least once.
341    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/// `FeatureValue` represents the types of dependencies a feature can have.
363#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
364pub enum FeatureValue {
365    /// A feature enabling another feature.
366    Feature(InternedString),
367    /// A feature enabling a dependency with `dep:dep_name` syntax.
368    Dep { dep_name: InternedString },
369    /// A feature enabling a feature on a dependency with `crate_name/feat_name` syntax.
370    DepFeature {
371        dep_name: InternedString,
372        dep_feature: InternedString,
373        /// If `true`, indicates the `?` syntax is used, which means this will
374        /// not automatically enable the dependency unless the dependency is
375        /// activated through some other means.
376        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    /// Returns the name of the dependency if and only if it was explicitly named with the `dep:` syntax.
404    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>>;