cargo/core/
summary.rs

1use std::borrow::Borrow;
2use std::collections::{BTreeMap, HashMap};
3use std::fmt::Display;
4use std::hash::{Hash, Hasher};
5use std::mem;
6use std::rc::Rc;
7
8use serde::{Serialize, Serializer};
9
10use crate::core::interning::InternedString;
11use crate::core::{Dependency, PackageId, SourceId};
12use semver::Version;
13
14use crate::util::CargoResult;
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: Rc<Inner>,
23}
24
25#[derive(Debug, Clone)]
26struct Inner {
27    package_id: PackageId,
28    dependencies: Vec<Dependency>,
29    features: Rc<FeatureMap>,
30    checksum: Option<String>,
31    links: Option<InternedString>,
32    namespaced_features: bool,
33}
34
35impl Summary {
36    pub fn new<K>(
37        pkg_id: PackageId,
38        dependencies: Vec<Dependency>,
39        features: &BTreeMap<K, Vec<impl AsRef<str>>>,
40        links: Option<impl Into<InternedString>>,
41        namespaced_features: bool,
42    ) -> CargoResult<Summary>
43    where
44        K: Borrow<str> + Ord + Display,
45    {
46        for dep in dependencies.iter() {
47            let feature = dep.name_in_toml();
48            if !namespaced_features && features.get(&*feature).is_some() {
49                anyhow::bail!(
50                    "Features and dependencies cannot have the \
51                     same name: `{}`",
52                    feature
53                )
54            }
55            if dep.is_optional() && !dep.is_transitive() {
56                anyhow::bail!(
57                    "Dev-dependencies are not allowed to be optional: `{}`",
58                    feature
59                )
60            }
61        }
62        let feature_map = build_feature_map(features, &dependencies, namespaced_features)?;
63        Ok(Summary {
64            inner: Rc::new(Inner {
65                package_id: pkg_id,
66                dependencies,
67                features: Rc::new(feature_map),
68                checksum: None,
69                links: links.map(|l| l.into()),
70                namespaced_features,
71            }),
72        })
73    }
74
75    pub fn package_id(&self) -> PackageId {
76        self.inner.package_id
77    }
78    pub fn name(&self) -> InternedString {
79        self.package_id().name()
80    }
81    pub fn version(&self) -> &Version {
82        self.package_id().version()
83    }
84    pub fn source_id(&self) -> SourceId {
85        self.package_id().source_id()
86    }
87    pub fn dependencies(&self) -> &[Dependency] {
88        &self.inner.dependencies
89    }
90    pub fn features(&self) -> &FeatureMap {
91        &self.inner.features
92    }
93    pub fn checksum(&self) -> Option<&str> {
94        self.inner.checksum.as_deref()
95    }
96    pub fn links(&self) -> Option<InternedString> {
97        self.inner.links
98    }
99    pub fn namespaced_features(&self) -> bool {
100        self.inner.namespaced_features
101    }
102
103    pub fn override_id(mut self, id: PackageId) -> Summary {
104        Rc::make_mut(&mut self.inner).package_id = id;
105        self
106    }
107
108    pub fn set_checksum(&mut self, cksum: String) {
109        Rc::make_mut(&mut self.inner).checksum = Some(cksum);
110    }
111
112    pub fn map_dependencies<F>(mut self, f: F) -> Summary
113    where
114        F: FnMut(Dependency) -> Dependency,
115    {
116        {
117            let slot = &mut Rc::make_mut(&mut self.inner).dependencies;
118            let deps = mem::replace(slot, Vec::new());
119            *slot = deps.into_iter().map(f).collect();
120        }
121        self
122    }
123
124    pub fn map_source(self, to_replace: SourceId, replace_with: SourceId) -> Summary {
125        let me = if self.package_id().source_id() == to_replace {
126            let new_id = self.package_id().with_source_id(replace_with);
127            self.override_id(new_id)
128        } else {
129            self
130        };
131        me.map_dependencies(|dep| dep.map_source(to_replace, replace_with))
132    }
133}
134
135impl PartialEq for Summary {
136    fn eq(&self, other: &Summary) -> bool {
137        self.inner.package_id == other.inner.package_id
138    }
139}
140
141impl Eq for Summary {}
142
143impl Hash for Summary {
144    fn hash<H: Hasher>(&self, state: &mut H) {
145        self.inner.package_id.hash(state);
146    }
147}
148
149// Checks features for errors, bailing out a CargoResult:Err if invalid,
150// and creates FeatureValues for each feature.
151fn build_feature_map<K>(
152    features: &BTreeMap<K, Vec<impl AsRef<str>>>,
153    dependencies: &[Dependency],
154    namespaced: bool,
155) -> CargoResult<FeatureMap>
156where
157    K: Borrow<str> + Ord + Display,
158{
159    use self::FeatureValue::*;
160    let mut dep_map = HashMap::new();
161    for dep in dependencies.iter() {
162        dep_map
163            .entry(dep.name_in_toml())
164            .or_insert_with(Vec::new)
165            .push(dep);
166    }
167
168    let mut map = BTreeMap::new();
169    for (feature, list) in features.iter() {
170        // If namespaced features is active and the key is the same as that of an
171        // optional dependency, that dependency must be included in the values.
172        // Thus, if a `feature` is found that has the same name as a dependency, we
173        // (a) bail out if the dependency is non-optional, and (b) we track if the
174        // feature requirements include the dependency `crate:feature` in the list.
175        // This is done with the `dependency_found` variable, which can only be
176        // false if features are namespaced and the current feature key is the same
177        // as the name of an optional dependency. If so, it gets set to true during
178        // iteration over the list if the dependency is found in the list.
179        let mut dependency_found = if namespaced {
180            match dep_map.get(feature.borrow()) {
181                Some(dep_data) => {
182                    if !dep_data.iter().any(|d| d.is_optional()) {
183                        anyhow::bail!(
184                            "Feature `{}` includes the dependency of the same name, but this is \
185                             left implicit in the features included by this feature.\n\
186                             Additionally, the dependency must be marked as optional to be \
187                             included in the feature definition.\n\
188                             Consider adding `crate:{}` to this feature's requirements \
189                             and marking the dependency as `optional = true`",
190                            feature,
191                            feature
192                        )
193                    } else {
194                        false
195                    }
196                }
197                None => true,
198            }
199        } else {
200            true
201        };
202
203        let mut values = vec![];
204        for dep in list {
205            let val = FeatureValue::build(
206                InternedString::new(dep.as_ref()),
207                |fs| features.contains_key(fs.as_str()),
208                namespaced,
209            );
210
211            // Find data for the referenced dependency...
212            let dep_data = {
213                match val {
214                    Feature(ref dep_name) | Crate(ref dep_name) | CrateFeature(ref dep_name, _) => {
215                        dep_map.get(dep_name.as_str())
216                    }
217                }
218            };
219            let is_optional_dep = dep_data
220                .iter()
221                .flat_map(|d| d.iter())
222                .any(|d| d.is_optional());
223            if let FeatureValue::Crate(ref dep_name) = val {
224                // If we have a dependency value, check if this is the dependency named
225                // the same as the feature that we were looking for.
226                if !dependency_found && feature.borrow() == dep_name.as_str() {
227                    dependency_found = true;
228                }
229            }
230
231            match (&val, dep_data.is_some(), is_optional_dep) {
232                // The value is a feature. If features are namespaced, this just means
233                // it's not prefixed with `crate:`, so we have to check whether the
234                // feature actually exist. If the feature is not defined *and* an optional
235                // dependency of the same name exists, the feature is defined implicitly
236                // here by adding it to the feature map, pointing to the dependency.
237                // If features are not namespaced, it's been validated as a feature already
238                // while instantiating the `FeatureValue` in `FeatureValue::build()`, so
239                // we don't have to do so here.
240                (&Feature(feat), _, true) => {
241                    if namespaced && !features.contains_key(&*feat) {
242                        map.insert(feat, vec![FeatureValue::Crate(feat)]);
243                    }
244                }
245                // If features are namespaced and the value is not defined as a feature
246                // and there is no optional dependency of the same name, error out.
247                // If features are not namespaced, there must be an existing feature
248                // here (checked by `FeatureValue::build()`), so it will always be defined.
249                (&Feature(feat), dep_exists, false) => {
250                    if namespaced && !features.contains_key(&*feat) {
251                        if dep_exists {
252                            anyhow::bail!(
253                                "Feature `{}` includes `{}` which is not defined as a feature.\n\
254                                 A non-optional dependency of the same name is defined; consider \
255                                 adding `optional = true` to its definition",
256                                feature,
257                                feat
258                            )
259                        } else {
260                            anyhow::bail!(
261                                "Feature `{}` includes `{}` which is not defined as a feature",
262                                feature,
263                                feat
264                            )
265                        }
266                    }
267                }
268                // The value is a dependency. If features are namespaced, it is explicitly
269                // tagged as such (`crate:value`). If features are not namespaced, any value
270                // not recognized as a feature is pegged as a `Crate`. Here we handle the case
271                // where the dependency exists but is non-optional. It branches on namespaced
272                // just to provide the correct string for the crate dependency in the error.
273                (&Crate(ref dep), true, false) => {
274                    if namespaced {
275                        anyhow::bail!(
276                            "Feature `{}` includes `crate:{}` which is not an \
277                             optional dependency.\nConsider adding \
278                             `optional = true` to the dependency",
279                            feature,
280                            dep
281                        )
282                    } else {
283                        anyhow::bail!(
284                            "Feature `{}` depends on `{}` which is not an \
285                             optional dependency.\nConsider adding \
286                             `optional = true` to the dependency",
287                            feature,
288                            dep
289                        )
290                    }
291                }
292                // If namespaced, the value was tagged as a dependency; if not namespaced,
293                // this could be anything not defined as a feature. This handles the case
294                // where no such dependency is actually defined; again, the branch on
295                // namespaced here is just to provide the correct string in the error.
296                (&Crate(ref dep), false, _) => {
297                    if namespaced {
298                        anyhow::bail!(
299                            "Feature `{}` includes `crate:{}` which is not a known \
300                             dependency",
301                            feature,
302                            dep
303                        )
304                    } else {
305                        anyhow::bail!(
306                            "Feature `{}` includes `{}` which is neither a dependency nor \
307                             another feature",
308                            feature,
309                            dep
310                        )
311                    }
312                }
313                (&Crate(_), true, true) => {}
314                // If the value is a feature for one of the dependencies, bail out if no such
315                // dependency is actually defined in the manifest.
316                (&CrateFeature(ref dep, _), false, _) => anyhow::bail!(
317                    "Feature `{}` requires a feature of `{}` which is not a \
318                     dependency",
319                    feature,
320                    dep
321                ),
322                (&CrateFeature(_, _), true, _) => {}
323            }
324            values.push(val);
325        }
326
327        if !dependency_found {
328            // If we have not found the dependency of the same-named feature, we should
329            // bail here.
330            anyhow::bail!(
331                "Feature `{}` includes the optional dependency of the \
332                 same name, but this is left implicit in the features \
333                 included by this feature.\nConsider adding \
334                 `crate:{}` to this feature's requirements.",
335                feature,
336                feature
337            )
338        }
339
340        map.insert(InternedString::new(feature.borrow()), values);
341    }
342    Ok(map)
343}
344
345/// FeatureValue represents the types of dependencies a feature can have:
346///
347/// * Another feature
348/// * An optional dependency
349/// * A feature in a dependency
350///
351/// The selection between these 3 things happens as part of the construction of the FeatureValue.
352#[derive(Clone, Debug)]
353pub enum FeatureValue {
354    Feature(InternedString),
355    Crate(InternedString),
356    CrateFeature(InternedString, InternedString),
357}
358
359impl FeatureValue {
360    fn build<T>(feature: InternedString, is_feature: T, namespaced: bool) -> FeatureValue
361    where
362        T: Fn(InternedString) -> bool,
363    {
364        match (feature.find('/'), namespaced) {
365            (Some(pos), _) => {
366                let (dep, dep_feat) = feature.split_at(pos);
367                let dep_feat = &dep_feat[1..];
368                FeatureValue::CrateFeature(InternedString::new(dep), InternedString::new(dep_feat))
369            }
370            (None, true) if feature.starts_with("crate:") => {
371                FeatureValue::Crate(InternedString::new(&feature[6..]))
372            }
373            (None, true) => FeatureValue::Feature(feature),
374            (None, false) if is_feature(feature) => FeatureValue::Feature(feature),
375            (None, false) => FeatureValue::Crate(feature),
376        }
377    }
378
379    pub fn new(feature: InternedString, s: &Summary) -> FeatureValue {
380        Self::build(
381            feature,
382            |fs| s.features().contains_key(&fs),
383            s.namespaced_features(),
384        )
385    }
386
387    pub fn to_string(&self, s: &Summary) -> String {
388        use self::FeatureValue::*;
389        match *self {
390            Feature(ref f) => f.to_string(),
391            Crate(ref c) => {
392                if s.namespaced_features() {
393                    format!("crate:{}", &c)
394                } else {
395                    c.to_string()
396                }
397            }
398            CrateFeature(ref c, ref f) => [c.as_ref(), f.as_ref()].join("/"),
399        }
400    }
401}
402
403impl Serialize for FeatureValue {
404    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
405    where
406        S: Serializer,
407    {
408        use self::FeatureValue::*;
409        match *self {
410            Feature(ref f) => serializer.serialize_str(f),
411            Crate(ref c) => serializer.serialize_str(c),
412            CrateFeature(ref c, ref f) => {
413                serializer.serialize_str(&[c.as_ref(), f.as_ref()].join("/"))
414            }
415        }
416    }
417}
418
419pub type FeatureMap = BTreeMap<InternedString, Vec<FeatureValue>>;