Skip to main content

cargo/workspace/
summary.rs

1use crate::util::CargoResult;
2use crate::util::closest_msg;
3use crate::util::data_structures::{HashMap, HashSet};
4use crate::util::interning::InternedString;
5use crate::workspace::{Dependency, PackageId, SourceId};
6use anyhow::bail;
7use cargo_util_schemas::manifest::FeatureName;
8use cargo_util_schemas::manifest::RustVersion;
9use semver::Version;
10use std::collections::BTreeMap;
11use std::fmt;
12use std::hash::{Hash, Hasher};
13use std::mem;
14use std::sync::Arc;
15
16/// Subset of a `Manifest`. Contains only the most important information about
17/// a package.
18///
19/// Summaries are cloned, and should not be mutated after creation
20#[derive(Debug, Clone)]
21pub struct Summary {
22    inner: Arc<Inner>,
23}
24
25#[derive(Debug, Clone)]
26struct Inner {
27    package_id: PackageId,
28    dependencies: Vec<Dependency>,
29    features: Arc<FeatureMap>,
30    checksum: Option<String>,
31    links: Option<InternedString>,
32    rust_version: Option<RustVersion>,
33    pubtime: Option<jiff::Timestamp>,
34}
35
36/// Indicates the dependency inferred from the `dep` syntax that should exist,
37/// but missing on the resolved dependencies tables.
38#[derive(Debug)]
39pub struct MissingDependencyError {
40    pub dep_name: InternedString,
41    pub feature: InternedString,
42    pub feature_value: FeatureValue,
43    /// Indicates the dependency inferred from the `dep?` syntax that is weak optional
44    pub weak_optional: bool,
45}
46
47impl std::error::Error for MissingDependencyError {}
48
49impl fmt::Display for MissingDependencyError {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        let Self {
52            dep_name,
53            feature,
54            feature_value: fv,
55            ..
56        } = self;
57
58        write!(
59            f,
60            "feature `{feature}` includes `{fv}`, but `{dep_name}` is not a dependency",
61        )
62    }
63}
64
65impl Summary {
66    #[tracing::instrument(skip_all)]
67    pub fn new(
68        pkg_id: PackageId,
69        dependencies: Vec<Dependency>,
70        features: &BTreeMap<InternedString, Vec<InternedString>>,
71        links: Option<impl Into<InternedString>>,
72        rust_version: Option<RustVersion>,
73    ) -> CargoResult<Summary> {
74        // ****CAUTION**** If you change anything here that may raise a new
75        // error, be sure to coordinate that change with either the index
76        // schema field or the SummariesCache version.
77        for dep in dependencies.iter() {
78            let dep_name = dep.name_in_toml();
79            if dep.is_optional() && !dep.is_transitive() {
80                bail!(
81                    "dev-dependencies are not allowed to be optional: `{}`",
82                    dep_name
83                )
84            }
85        }
86        let feature_map = build_feature_map(features, &dependencies)?;
87        Ok(Summary {
88            inner: Arc::new(Inner {
89                package_id: pkg_id,
90                dependencies,
91                features: Arc::new(feature_map),
92                checksum: None,
93                links: links.map(|l| l.into()),
94                rust_version,
95                pubtime: None,
96            }),
97        })
98    }
99
100    pub fn package_id(&self) -> PackageId {
101        self.inner.package_id
102    }
103    pub fn name(&self) -> InternedString {
104        self.package_id().name()
105    }
106    pub fn version(&self) -> &Version {
107        self.package_id().version()
108    }
109    pub fn source_id(&self) -> SourceId {
110        self.package_id().source_id()
111    }
112    pub fn dependencies(&self) -> &[Dependency] {
113        &self.inner.dependencies
114    }
115    pub fn features(&self) -> &FeatureMap {
116        &self.inner.features
117    }
118
119    pub fn checksum(&self) -> Option<&str> {
120        self.inner.checksum.as_deref()
121    }
122    pub fn links(&self) -> Option<InternedString> {
123        self.inner.links
124    }
125
126    pub fn rust_version(&self) -> Option<&RustVersion> {
127        self.inner.rust_version.as_ref()
128    }
129
130    pub fn pubtime(&self) -> Option<jiff::Timestamp> {
131        self.inner.pubtime
132    }
133
134    pub fn override_id(mut self, id: PackageId) -> Summary {
135        Arc::make_mut(&mut self.inner).package_id = id;
136        self
137    }
138
139    pub fn set_checksum(&mut self, cksum: String) {
140        Arc::make_mut(&mut self.inner).checksum = Some(cksum);
141    }
142
143    pub fn set_pubtime(&mut self, pubtime: jiff::Timestamp) {
144        Arc::make_mut(&mut self.inner).pubtime = Some(pubtime);
145    }
146
147    pub fn map_dependencies<F>(self, mut f: F) -> Summary
148    where
149        F: FnMut(Dependency) -> Dependency,
150    {
151        self.try_map_dependencies(|dep| Ok(f(dep))).unwrap()
152    }
153
154    pub fn try_map_dependencies<F>(mut self, f: F) -> CargoResult<Summary>
155    where
156        F: FnMut(Dependency) -> CargoResult<Dependency>,
157    {
158        {
159            let slot = &mut Arc::make_mut(&mut self.inner).dependencies;
160            *slot = mem::take(slot)
161                .into_iter()
162                .map(f)
163                .collect::<CargoResult<_>>()?;
164        }
165        Ok(self)
166    }
167
168    pub fn map_source(self, to_replace: SourceId, replace_with: SourceId) -> Summary {
169        let me = if self.package_id().source_id() == to_replace {
170            let new_id = self.package_id().with_source_id(replace_with);
171            self.override_id(new_id)
172        } else {
173            self
174        };
175        me.map_dependencies(|dep| dep.map_source(to_replace, replace_with))
176    }
177}
178
179impl PartialEq for Summary {
180    fn eq(&self, other: &Summary) -> bool {
181        self.inner.package_id == other.inner.package_id
182    }
183}
184
185impl Eq for Summary {}
186
187impl Hash for Summary {
188    fn hash<H: Hasher>(&self, state: &mut H) {
189        self.inner.package_id.hash(state);
190    }
191}
192
193// A check that only compiles if Summary is Sync
194const _: fn() = || {
195    fn is_sync<T: Sync>() {}
196    is_sync::<Summary>();
197};
198
199/// Checks features for errors, bailing out a CargoResult:Err if invalid,
200/// and creates `FeatureValues` for each feature.
201fn build_feature_map(
202    features: &BTreeMap<InternedString, Vec<InternedString>>,
203    dependencies: &[Dependency],
204) -> CargoResult<FeatureMap> {
205    use self::FeatureValue::*;
206    // A map of dependency names to whether there are any that are optional.
207    let mut dep_map: HashMap<InternedString, bool> = HashMap::default();
208    for dep in dependencies.iter() {
209        *dep_map.entry(dep.name_in_toml()).or_insert(false) |= dep.is_optional();
210    }
211    let dep_map = dep_map; // We are done mutating this variable
212
213    let mut map: FeatureMap = features
214        .iter()
215        .map(|(feature, list)| {
216            let fvs: Vec<_> = list
217                .iter()
218                .map(|feat_value| FeatureValue::new(*feat_value))
219                .collect();
220            (*feature, fvs)
221        })
222        .collect();
223
224    // Add implicit features for optional dependencies if they weren't
225    // explicitly listed anywhere.
226    let explicitly_listed: HashSet<_> = map
227        .values()
228        .flatten()
229        .filter_map(|fv| fv.explicit_dep_name())
230        .collect();
231
232    for dep in dependencies {
233        if !dep.is_optional() {
234            continue;
235        }
236        let dep_name = dep.name_in_toml();
237        if features.contains_key(&dep_name) || explicitly_listed.contains(&dep_name) {
238            continue;
239        }
240        map.insert(dep_name, vec![Dep { dep_name }]);
241    }
242    let map = map; // We are done mutating this variable
243
244    // Validate features are listed properly.
245    for (feature, fvs) in &map {
246        FeatureName::new(feature)?;
247        for fv in fvs {
248            // Find data for the referenced dependency...
249            let dep_data = dep_map.get(&fv.feature_or_dep_name());
250            let is_any_dep = dep_data.is_some();
251            let is_optional_dep = dep_data.is_some_and(|&o| o);
252            match fv {
253                Feature(f) => {
254                    if !features.contains_key(f) {
255                        if !is_any_dep {
256                            let closest = closest_msg(f, features.keys(), |k| k, "feature");
257                            bail!(
258                                "feature `{feature}` includes `{fv}` which is neither a dependency \
259                                 nor another feature{closest}"
260                            );
261                        }
262                        if is_optional_dep {
263                            if !map.contains_key(f) {
264                                bail!(
265                                    "feature `{feature}` includes `{fv}`, but `{f}` is an \
266                                     optional dependency without an implicit feature\n\
267                                     Use `dep:{f}` to enable the dependency."
268                                );
269                            }
270                        } else {
271                            bail!(
272                                "feature `{feature}` includes `{fv}`, but `{f}` 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                }
279                Dep { dep_name } => {
280                    if !is_any_dep {
281                        bail!(
282                            "feature `{feature}` includes `{fv}`, but `{dep_name}` is not listed as a dependency"
283                        );
284                    }
285                    if !is_optional_dep {
286                        bail!(
287                            "feature `{feature}` includes `{fv}`, but `{dep_name}` is not an optional dependency\n\
288                             A non-optional dependency of the same name is defined; \
289                             consider adding `optional = true` to its definition."
290                        );
291                    }
292                }
293                DepFeature {
294                    dep_name,
295                    dep_feature,
296                    weak,
297                } => {
298                    // Early check for some unlikely syntax.
299                    if dep_feature.contains('/') {
300                        bail!(
301                            "multiple slashes in feature `{fv}` (included by feature `{feature}`) are not allowed"
302                        );
303                    }
304
305                    // dep: cannot be combined with /
306                    if let Some(stripped_dep) = dep_name.strip_prefix("dep:") {
307                        let has_other_dep = explicitly_listed.contains(stripped_dep);
308                        let is_optional = dep_map.get(stripped_dep).is_some_and(|&o| o);
309                        let extra_help = if *weak || has_other_dep || !is_optional {
310                            // In this case, the user should just remove dep:.
311                            // Note that "hiding" an optional dependency
312                            // wouldn't work with just a single `dep:foo?/bar`
313                            // because there would not be any way to enable
314                            // `foo`.
315                            String::new()
316                        } else {
317                            format!(
318                                "\nIf the intent is to avoid creating an implicit feature \
319                                 `{stripped_dep}` for an optional dependency, \
320                                 then consider replacing this with two values:\n    \
321                                 \"dep:{stripped_dep}\", \"{stripped_dep}/{dep_feature}\""
322                            )
323                        };
324                        bail!(
325                            "feature `{feature}` includes `{fv}` with both `dep:` and `/`\n\
326                            To fix this, remove the `dep:` prefix.{extra_help}"
327                        )
328                    }
329
330                    // Validation of the feature name will be performed in the resolver.
331                    if !is_any_dep {
332                        bail!(MissingDependencyError {
333                            feature: *feature,
334                            feature_value: (*fv).clone(),
335                            dep_name: *dep_name,
336                            weak_optional: *weak,
337                        })
338                    }
339                    if *weak && !is_optional_dep {
340                        bail!(
341                            "feature `{feature}` includes `{fv}` with a `?`, but `{dep_name}` is not an optional dependency\n\
342                            A non-optional dependency of the same name is defined; \
343                            consider removing the `?` or changing the dependency to be optional"
344                        );
345                    }
346                }
347            }
348        }
349    }
350
351    // Make sure every optional dep is mentioned at least once.
352    let used: HashSet<_> = map
353        .values()
354        .flatten()
355        .filter_map(|fv| match fv {
356            Dep { dep_name } | DepFeature { dep_name, .. } => Some(dep_name),
357            _ => None,
358        })
359        .collect();
360    if let Some((dep, _)) = dep_map
361        .iter()
362        .find(|&(dep, &is_optional)| is_optional && !used.contains(dep))
363    {
364        bail!(
365            "optional dependency `{dep}` is not included in any feature\n\
366            Make sure that `dep:{dep}` is included in one of features in the [features] table."
367        );
368    }
369
370    Ok(map)
371}
372
373/// `FeatureValue` represents the types of dependencies a feature can have.
374#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
375pub enum FeatureValue {
376    /// A feature enabling another feature.
377    Feature(InternedString),
378    /// A feature enabling a dependency with `dep:dep_name` syntax.
379    Dep { dep_name: InternedString },
380    /// A feature enabling a feature on a dependency with `crate_name/feat_name` syntax.
381    DepFeature {
382        dep_name: InternedString,
383        dep_feature: InternedString,
384        /// If `true`, indicates the `?` syntax is used, which means this will
385        /// not automatically enable the dependency unless the dependency is
386        /// activated through some other means.
387        weak: bool,
388    },
389}
390
391impl FeatureValue {
392    pub fn new(feature: InternedString) -> FeatureValue {
393        match feature.split_once('/') {
394            Some((dep, dep_feat)) => {
395                let dep_name = dep.strip_suffix('?');
396                FeatureValue::DepFeature {
397                    dep_name: dep_name.unwrap_or(dep).into(),
398                    dep_feature: dep_feat.into(),
399                    weak: dep_name.is_some(),
400                }
401            }
402            None => {
403                if let Some(dep_name) = feature.strip_prefix("dep:") {
404                    FeatureValue::Dep {
405                        dep_name: dep_name.into(),
406                    }
407                } else {
408                    FeatureValue::Feature(feature)
409                }
410            }
411        }
412    }
413
414    /// Returns the name of the dependency if and only if it was explicitly named with the `dep:` syntax.
415    fn explicit_dep_name(&self) -> Option<InternedString> {
416        match self {
417            FeatureValue::Dep { dep_name, .. } => Some(*dep_name),
418            _ => None,
419        }
420    }
421
422    fn feature_or_dep_name(&self) -> InternedString {
423        match self {
424            FeatureValue::Feature(dep_name)
425            | FeatureValue::Dep { dep_name, .. }
426            | FeatureValue::DepFeature { dep_name, .. } => *dep_name,
427        }
428    }
429}
430
431impl fmt::Display for FeatureValue {
432    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
433        use self::FeatureValue::*;
434        match self {
435            Feature(feat) => write!(f, "{feat}"),
436            Dep { dep_name } => write!(f, "dep:{dep_name}"),
437            DepFeature {
438                dep_name,
439                dep_feature,
440                weak,
441            } => {
442                let weak = if *weak { "?" } else { "" };
443                write!(f, "{dep_name}{weak}/{dep_feature}")
444            }
445        }
446    }
447}
448
449pub type FeatureMap = BTreeMap<InternedString, Vec<FeatureValue>>;