Fix optional deps in multiple sections
authorAlex Crichton <alex@alexcrichton.com>
Fri, 4 May 2018 16:19:30 +0000 (09:19 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Fri, 4 May 2018 21:06:59 +0000 (14:06 -0700)
This commit fixes an issue where an optional dependency was listed multiple
times in a manifest (multiple sections). This regression was introduced by #5415
and happened because in the resolver we didn't record a `Dependency` as it was
accidentally deduplicated too soon.

The fix here was to ensure that all `Dependency` annotations make their way into
`Resolve` now that we rely on the listed `Dependency` values for correctness.

Closes #5475

src/cargo/core/resolver/context.rs
tests/testsuite/build_script.rs

index a41f482c1ba3781263710ecc9835b31e651c5ad6..4b089dbe17df6fcec51e8485898083a1962b84b4 100644 (file)
@@ -187,18 +187,23 @@ impl Context {
         } else {
             vec![]
         };
-        let mut reqs = build_requirements(s, method, &values)?;
+        let reqs = build_requirements(s, method, &values)?;
         let mut ret = Vec::new();
+        let mut used_features = HashSet::new();
+        let default_dep = (false, Vec::new());
 
         // Next, collect all actually enabled dependencies and their features.
         for dep in deps {
-            // Skip optional dependencies, but not those enabled through a feature
+            // Skip optional dependencies, but not those enabled through a
+            // feature
             if dep.is_optional() && !reqs.deps.contains_key(&*dep.name()) {
                 continue;
             }
-            // So we want this dependency.  Move the features we want from `feature_deps`
-            // to `ret`.
-            let base = reqs.deps.remove(&*dep.name()).unwrap_or((false, vec![]));
+            // So we want this dependency. Move the features we want from
+            // `feature_deps` to `ret` and register ourselves as using this
+            // name.
+            let base = reqs.deps.get(&*dep.name()).unwrap_or(&default_dep);
+            used_features.insert(dep.name().as_str());
             if !dep.is_optional() && base.0 {
                 self.warnings.push(format!(
                     "Package `{}` does not have feature `{}`. It has a required dependency \
@@ -209,7 +214,7 @@ impl Context {
                     dep.name()
                 ));
             }
-            let mut base = base.1;
+            let mut base = base.1.clone();
             base.extend(dep.features().iter());
             for feature in base.iter() {
                 if feature.contains('/') {
@@ -221,12 +226,16 @@ impl Context {
             ret.push((dep.clone(), base));
         }
 
-        // Any remaining entries in feature_deps are bugs in that the package does not actually
-        // have those dependencies.  We classified them as dependencies in the first place
-        // because there is no such feature, either.
-        if !reqs.deps.is_empty() {
-            let unknown = reqs.deps.keys().map(|s| &s[..]).collect::<Vec<&str>>();
-            let features = unknown.join(", ");
+        // Any entries in `reqs.dep` which weren't used are bugs in that the
+        // package does not actually have those dependencies. We classified
+        // them as dependencies in the first place because there is no such
+        // feature, either.
+        let remaining = reqs.deps.keys()
+            .cloned()
+            .filter(|s| !used_features.contains(s))
+            .collect::<Vec<_>>();
+        if !remaining.is_empty() {
+            let features = remaining.join(", ");
             return Err(match parent {
                 None => format_err!(
                     "Package `{}` does not have these features: `{}`",
index b3cf382e2a7d9370c779aa53756fa201082205eb..1add770c2cc58ce7b00654b6dbbfff47d77540dc 100644 (file)
@@ -3833,3 +3833,68 @@ fn rename_with_link_search_path() {
         ),
     );
 }
+
+#[test]
+fn optional_build_script_dep() {
+    let p = project("foo")
+        .file(
+            "Cargo.toml",
+            r#"
+                [project]
+                name = "foo"
+                version = "0.5.0"
+                authors = []
+
+                [dependencies]
+                bar = { path = "bar", optional = true }
+
+                [build-dependencies]
+                bar = { path = "bar", optional = true }
+            "#,
+        )
+        .file("build.rs", r#"
+            #[cfg(feature = "bar")]
+            extern crate bar;
+
+            fn main() {
+                #[cfg(feature = "bar")] {
+                    println!("cargo:rustc-env=FOO={}", bar::bar());
+                    return
+                }
+                println!("cargo:rustc-env=FOO=0");
+            }
+        "#)
+        .file(
+            "src/main.rs",
+            r#"
+                #[cfg(feature = "bar")]
+                extern crate bar;
+
+                fn main() {
+                    println!("{}", env!("FOO"));
+                }
+            "#,
+        )
+        .file(
+            "bar/Cargo.toml",
+            r#"
+                [project]
+                name = "bar"
+                version = "0.5.0"
+                authors = []
+            "#,
+        )
+        .file(
+            "bar/src/lib.rs",
+            r#"
+                pub fn bar() -> u32 { 1 }
+            "#,
+        );
+    let p = p.build();
+
+    assert_that(p.cargo("run"), execs().with_status(0).with_stdout("0\n"));
+    assert_that(
+        p.cargo("run --features bar"),
+        execs().with_status(0).with_stdout("1\n"),
+    );
+}