Introduce FeatureValue enum for tracking feature types
authorDirkjan Ochtman <dirkjan@ochtman.nl>
Sun, 15 Oct 2017 12:23:33 +0000 (14:23 +0200)
committerDirkjan Ochtman <dirkjan@ochtman.nl>
Wed, 4 Apr 2018 16:03:40 +0000 (18:03 +0200)
src/cargo/core/interning.rs
src/cargo/core/mod.rs
src/cargo/core/resolver/context.rs
src/cargo/core/summary.rs
src/cargo/ops/registry.rs
tests/testsuite/metadata.rs

index 2f8cf7451fa4eb6d2bd0a9152cd83f5819b20504..d8c18df2d8411eac8358d9dc8471a41dbeb0c475 100644 (file)
@@ -1,3 +1,5 @@
+use serde::{Serialize, Serializer};
+
 use std::fmt;
 use std::sync::RwLock;
 use std::collections::HashSet;
@@ -95,3 +97,12 @@ impl PartialOrd for InternedString {
         Some(self.cmp(other))
     }
 }
+
+impl Serialize for InternedString {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        serializer.serialize_str(self.inner)
+    }
+}
index 6b8926756e1f118ea9630eed6946d5fc7ee90d26..2f7db061a2157198feb0c0001e6ca4644b779ba5 100644 (file)
@@ -9,7 +9,7 @@ pub use self::registry::Registry;
 pub use self::resolver::Resolve;
 pub use self::shell::{Shell, Verbosity};
 pub use self::source::{GitReference, Source, SourceId, SourceMap};
-pub use self::summary::{FeatureMap, Summary};
+pub use self::summary::{FeatureMap, FeatureValue, Summary};
 pub use self::workspace::{Members, Workspace, WorkspaceConfig, WorkspaceRootConfig};
 
 pub mod source;
index a1f97fe3c251b48d5048776d97d4336b93d91e08..2de77d39fc31c6b17298f9755da8919b4092af15 100644 (file)
@@ -1,7 +1,7 @@
 use std::collections::{HashMap, HashSet};
 use std::rc::Rc;
 
-use core::{Dependency, PackageId, SourceId, Summary};
+use core::{Dependency, FeatureValue, PackageId, SourceId, Summary};
 use core::interning::InternedString;
 use util::Graph;
 use util::CargoResult;
@@ -170,7 +170,24 @@ impl Context {
         let deps = s.dependencies();
         let deps = deps.iter().filter(|d| d.is_transitive() || dev_deps);
 
-        let mut reqs = build_requirements(s, method)?;
+        // Requested features stored in the Method are stored as string references, but we want to
+        // transform them into FeatureValues here. In order to pass the borrow checker with
+        // storage of the FeatureValues that outlives the Requirements object, we do the
+        // transformation here, and pass the FeatureValues to build_requirements().
+        let values = if let Method::Required {
+            all_features: false,
+            features: requested,
+            ..
+        } = *method
+        {
+            requested
+                .iter()
+                .map(|f| FeatureValue::new(f, s))
+                .collect::<Vec<FeatureValue>>()
+        } else {
+            vec![]
+        };
+        let mut reqs = build_requirements(s, method, &values)?;
         let mut ret = Vec::new();
 
         // Next, collect all actually enabled dependencies and their features.
@@ -269,8 +286,12 @@ impl Context {
 fn build_requirements<'a, 'b: 'a>(
     s: &'a Summary,
     method: &'b Method,
+    requested: &'a [FeatureValue],
 ) -> CargoResult<Requirements<'a>> {
     let mut reqs = Requirements::new(s);
+    for fv in requested.iter() {
+        reqs.require_value(fv)?;
+    }
     match *method {
         Method::Everything
         | Method::Required {
@@ -283,12 +304,7 @@ fn build_requirements<'a, 'b: 'a>(
                 reqs.require_dependency(dep.name().as_str());
             }
         }
-        Method::Required {
-            features: requested_features,
-            ..
-        } => for feat in requested_features.iter() {
-            reqs.add_feature(feat)?;
-        },
+        _ => {} // Explicitly requested features are handled through `requested`
     }
     match *method {
         Method::Everything
@@ -359,50 +375,34 @@ impl<'r> Requirements<'r> {
     }
 
     fn require_feature(&mut self, feat: &'r str) -> CargoResult<()> {
-        if self.seen(feat) {
+        if feat.is_empty() || self.seen(feat) {
             return Ok(());
         }
-        for f in self.summary
+        for fv in self.summary
             .features()
             .get(feat)
             .expect("must be a valid feature")
         {
-            if f == feat {
-                bail!(
+            match *fv {
+                FeatureValue::Feature(ref dep_feat) if **dep_feat == *feat => bail!(
                     "Cyclic feature dependency: feature `{}` depends on itself",
                     feat
-                );
+                ),
+                _ => {}
             }
-            self.add_feature(f)?;
+            self.require_value(&fv)?;
         }
         Ok(())
     }
 
-    fn add_feature(&mut self, feat: &'r str) -> CargoResult<()> {
-        if feat.is_empty() {
-            return Ok(());
-        }
-
-        // If this feature is of the form `foo/bar`, then we just lookup package
-        // `foo` and enable its feature `bar`. Otherwise this feature is of the
-        // form `foo` and we need to recurse to enable the feature `foo` for our
-        // own package, which may end up enabling more features or just enabling
-        // a dependency.
-        let mut parts = feat.splitn(2, '/');
-        let feat_or_package = parts.next().unwrap();
-        match parts.next() {
-            Some(feat) => {
-                self.require_crate_feature(feat_or_package, feat);
-            }
-            None => {
-                if self.summary.features().contains_key(feat_or_package) {
-                    self.require_feature(feat_or_package)?;
-                } else {
-                    self.require_dependency(feat_or_package);
-                }
+    fn require_value(&mut self, fv: &'r FeatureValue) -> CargoResult<()> {
+        match *fv {
+            FeatureValue::Feature(ref feat) => self.require_feature(feat),
+            FeatureValue::Crate(ref dep) => Ok(self.require_dependency(dep)),
+            FeatureValue::CrateFeature(ref dep, ref dep_feat) => {
+                Ok(self.require_crate_feature(dep, dep_feat))
             }
         }
-        Ok(())
     }
 }
 
index 1081b5d6c4be34fb6e893d2b7e680ee89085e526..34cdc72b5f72ebc119c504fbdc8c17289b2aef64 100644 (file)
@@ -48,47 +48,60 @@ impl Summary {
                 )
             }
         }
+        let mut features_new = BTreeMap::new();
         for (feature, list) in features.iter() {
-            for dep in list.iter() {
-                let mut parts = dep.splitn(2, '/');
-                let dep = parts.next().unwrap();
-                let is_reexport = parts.next().is_some();
-                if !is_reexport && features.get(dep).is_some() {
+            let mut values = vec![];
+            for dep in list {
+                use self::FeatureValue::*;
+                let val = FeatureValue::build(dep, |fs| (&features).get(fs).is_some());
+                if let &Feature(_) = &val {
+                    // Return early to avoid doing unnecessary work
+                    values.push(val);
                     continue;
                 }
-                match dependencies.iter().find(|d| &*d.name() == dep) {
-                    Some(d) => {
-                        if d.is_optional() || is_reexport {
-                            continue;
+                // Find data for the referenced dependency...
+                let dep_data = {
+                    let dep_name = match &val {
+                        &Feature(_) => "",
+                        &Crate(ref dep_name) | &CrateFeature(ref dep_name, _) => dep_name,
+                    };
+                    dependencies.iter().find(|d| *d.name() == *dep_name)
+                };
+                match (&val, dep_data) {
+                    (&Crate(ref dep), Some(d)) => {
+                        if !d.is_optional() {
+                            bail!(
+                                "Feature `{}` depends on `{}` which is not an \
+                                 optional dependency.\nConsider adding \
+                                 `optional = true` to the dependency",
+                                feature,
+                                dep
+                            )
                         }
-                        bail!(
-                            "Feature `{}` depends on `{}` which is not an \
-                             optional dependency.\nConsider adding \
-                             `optional = true` to the dependency",
-                            feature,
-                            dep
-                        )
                     }
-                    None if is_reexport => bail!(
+                    (&CrateFeature(ref dep_name, _), None) => bail!(
                         "Feature `{}` requires a feature of `{}` which is not a \
                          dependency",
                         feature,
-                        dep
+                        dep_name
                     ),
-                    None => bail!(
+                    (&Crate(ref dep), None) => bail!(
                         "Feature `{}` includes `{}` which is neither \
                          a dependency nor another feature",
                         feature,
                         dep
                     ),
+                    (&CrateFeature(_, _), Some(_)) | (&Feature(_), _) => {}
                 }
+                values.push(val);
             }
+            features_new.insert(feature.clone(), values);
         }
         Ok(Summary {
             inner: Rc::new(Inner {
                 package_id: pkg_id,
                 dependencies,
-                features,
+                features: features_new,
                 checksum: None,
                 links: links.map(|l| InternedString::new(&l)),
             }),
@@ -159,4 +172,48 @@ impl PartialEq for Summary {
     }
 }
 
-pub type FeatureMap = BTreeMap<String, Vec<String>>;
+/// FeatureValue represents the types of dependencies a feature can have:
+///
+/// * Another feature
+/// * An optional dependency
+/// * A feature in a depedency
+///
+/// The selection between these 3 things happens as part of the construction of the FeatureValue.
+#[derive(Clone, Debug, Serialize)]
+pub enum FeatureValue {
+    Feature(InternedString),
+    Crate(InternedString),
+    CrateFeature(InternedString, InternedString),
+}
+
+impl FeatureValue {
+    fn build<T>(feature: &str, is_feature: T) -> FeatureValue
+    where
+        T: Fn(&str) -> bool,
+    {
+        match feature.find('/') {
+            Some(pos) => {
+                let (dep, dep_feat) = feature.split_at(pos);
+                let dep_feat = &dep_feat[1..];
+                FeatureValue::CrateFeature(InternedString::new(dep), InternedString::new(dep_feat))
+            }
+            None if is_feature(&feature) => FeatureValue::Feature(InternedString::new(feature)),
+            None => FeatureValue::Crate(InternedString::new(feature)),
+        }
+    }
+
+    pub fn new(feature: &str, s: &Summary) -> FeatureValue {
+        Self::build(feature, |fs| s.features().get(fs).is_some())
+    }
+
+    pub fn to_string(&self) -> String {
+        use self::FeatureValue::*;
+        match *self {
+            Feature(ref f) => f.to_string(),
+            Crate(ref c) => c.to_string(),
+            CrateFeature(ref c, ref f) => [c.as_ref(), f.as_ref()].join("/"),
+        }
+    }
+}
+
+pub type FeatureMap = BTreeMap<String, Vec<FeatureValue>>;
index ed997ccb266df8cc696db432aec431a790fe9ff1..64f4a8cb57d237ddd11e7fa5cc219077d9bb9179 100644 (file)
@@ -1,4 +1,5 @@
 use std::{cmp, env};
+use std::collections::BTreeMap;
 use std::fs::{self, File};
 use std::iter::repeat;
 use std::time::Duration;
@@ -213,12 +214,23 @@ fn transmit(
         return Ok(());
     }
 
+    let string_features = pkg.summary()
+        .features()
+        .iter()
+        .map(|(feat, values)| {
+            (
+                feat.clone(),
+                values.iter().map(|fv| fv.to_string()).collect(),
+            )
+        })
+        .collect::<BTreeMap<String, Vec<String>>>();
+
     let publish = registry.publish(
         &NewCrate {
             name: pkg.name().to_string(),
             vers: pkg.version().to_string(),
             deps,
-            features: pkg.summary().features().clone(),
+            features: string_features,
             authors: authors.clone(),
             description: description.clone(),
             homepage: homepage.clone(),
index 4a2154bd9309187653b10de092e37469a5f951b9..34720c4b222d55bcd7123c388a23bfd81c5d11cb 100644 (file)
@@ -192,7 +192,9 @@ optional_feat = []
                 ],
                 "features": {
                   "default": [
-                    "default_feat"
+                    {
+                       "Feature": "default_feat"
+                    }
                   ],
                   "default_feat": [],
                   "optional_feat": []