cargo/core/
summary.rs

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/// 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!("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                    // Early check for some unlikely syntax.
284                    if dep_feature.contains('/') {
285                        bail!("multiple slashes in feature `{fv}` (included by feature `{feature}`) are not allowed");
286                    }
287
288                    // dep: cannot be combined with /
289                    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                            // In this case, the user should just remove dep:.
294                            // Note that "hiding" an optional dependency
295                            // wouldn't work with just a single `dep:foo?/bar`
296                            // because there would not be any way to enable
297                            // `foo`.
298                            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                    // Validation of the feature name will be performed in the resolver.
314                    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    // Make sure every optional dep is mentioned at least once.
335    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/// `FeatureValue` represents the types of dependencies a feature can have.
357#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
358pub enum FeatureValue {
359    /// A feature enabling another feature.
360    Feature(InternedString),
361    /// A feature enabling a dependency with `dep:dep_name` syntax.
362    Dep { dep_name: InternedString },
363    /// A feature enabling a feature on a dependency with `crate_name/feat_name` syntax.
364    DepFeature {
365        dep_name: InternedString,
366        dep_feature: InternedString,
367        /// If `true`, indicates the `?` syntax is used, which means this will
368        /// not automatically enable the dependency unless the dependency is
369        /// activated through some other means.
370        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: InternedString::new(dep_name.unwrap_or(dep)),
381                    dep_feature: InternedString::new(dep_feat),
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: InternedString::new(dep_name),
389                    }
390                } else {
391                    FeatureValue::Feature(feature)
392                }
393            }
394        }
395    }
396
397    /// Returns the name of the dependency if and only if it was explicitly named with the `dep:` syntax.
398    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>>;