Introduce autoXXX keys for target auto-discovery
authorDale Wijnand <dale.wijnand@gmail.com>
Mon, 16 Apr 2018 22:19:42 +0000 (00:19 +0200)
committerDale Wijnand <dale.wijnand@gmail.com>
Fri, 20 Apr 2018 19:57:24 +0000 (20:57 +0100)
In Rust 2015 absence of the configuration makes it default to not
include auto-discovered targets (i.e false), with a warnings message.

In Rust 2018 absence makes it default to include auto-discovered
targets (i.e true).

Fixes #5330

src/cargo/util/toml/mod.rs
src/cargo/util/toml/targets.rs
src/doc/src/reference/manifest.md
tests/testsuite/bench.rs
tests/testsuite/build.rs
tests/testsuite/run.rs

index bb9fb7b4de136bcc2aafdd61b9d853ec765a1c0a..c1daaf9091d7ce2211bab4c5e6841373748d7eb8 100644 (file)
@@ -459,6 +459,10 @@ pub struct TomlProject {
     workspace: Option<String>,
     #[serde(rename = "im-a-teapot")]
     im_a_teapot: Option<bool>,
+    autobins: Option<bool>,
+    autoexamples: Option<bool>,
+    autotests: Option<bool>,
+    autobenches: Option<bool>,
 
     // package metadata
     description: Option<String>,
@@ -653,6 +657,7 @@ impl TomlManifest {
             me,
             package_name,
             package_root,
+            edition,
             &project.build,
             &mut warnings,
             &mut errors,
index 182154ce65493a8bb0acc0210244195f3fa586d5..2384dc28cf8b6cee9251cc909d6970d1d7e09724 100644 (file)
@@ -14,7 +14,7 @@ use std::path::{Path, PathBuf};
 use std::fs::{self, DirEntry};
 use std::collections::HashSet;
 
-use core::{compiler, Target};
+use core::{compiler, Edition, Target};
 use util::errors::CargoResult;
 use super::{LibKind, PathValue, StringOrBool, TomlBenchTarget, TomlBinTarget, TomlExampleTarget,
             TomlLibTarget, TomlManifest, TomlTarget, TomlTestTarget};
@@ -23,6 +23,7 @@ pub fn targets(
     manifest: &TomlManifest,
     package_name: &str,
     package_root: &Path,
+    edition: Edition,
     custom_build: &Option<StringOrBool>,
     warnings: &mut Vec<String>,
     errors: &mut Vec<String>,
@@ -38,10 +39,18 @@ pub fn targets(
         has_lib = false;
     }
 
+    let package = manifest
+        .package
+        .as_ref()
+        .or_else(|| manifest.project.as_ref())
+        .ok_or_else(|| format_err!("manifest has no `package` (or `project`)"))?;
+
     targets.extend(clean_bins(
         manifest.bin.as_ref(),
         package_root,
         package_name,
+        edition,
+        package.autobins,
         warnings,
         errors,
         has_lib,
@@ -50,14 +59,26 @@ pub fn targets(
     targets.extend(clean_examples(
         manifest.example.as_ref(),
         package_root,
+        edition,
+        package.autoexamples,
+        warnings,
         errors,
     )?);
 
-    targets.extend(clean_tests(manifest.test.as_ref(), package_root, errors)?);
+    targets.extend(clean_tests(
+        manifest.test.as_ref(),
+        package_root,
+        edition,
+        package.autotests,
+        warnings,
+        errors,
+    )?);
 
     targets.extend(clean_benches(
         manifest.bench.as_ref(),
         package_root,
+        edition,
+        package.autobenches,
         warnings,
         errors,
     )?);
@@ -163,6 +184,8 @@ fn clean_bins(
     toml_bins: Option<&Vec<TomlBinTarget>>,
     package_root: &Path,
     package_name: &str,
+    edition: Edition,
+    autodiscover: Option<bool>,
     warnings: &mut Vec<String>,
     errors: &mut Vec<String>,
     has_lib: bool,
@@ -172,6 +195,13 @@ fn clean_bins(
     let bins = toml_targets_and_inferred(
         toml_bins,
         &inferred,
+        package_root,
+        autodiscover,
+        edition,
+        warnings,
+        "binary",
+        "bin",
+        "autobins",
     );
 
     for bin in &bins {
@@ -254,6 +284,9 @@ fn clean_bins(
 fn clean_examples(
     toml_examples: Option<&Vec<TomlExampleTarget>>,
     package_root: &Path,
+    edition: Edition,
+    autodiscover: Option<bool>,
+    warnings: &mut Vec<String>,
     errors: &mut Vec<String>,
 ) -> CargoResult<Vec<Target>> {
     let inferred = infer_from_directory(&package_root.join("examples"));
@@ -264,7 +297,11 @@ fn clean_examples(
         toml_examples,
         &inferred,
         package_root,
+        edition,
+        autodiscover,
+        warnings,
         errors,
+        "autoexamples",
     )?;
 
     let mut result = Vec::new();
@@ -290,11 +327,25 @@ fn clean_examples(
 fn clean_tests(
     toml_tests: Option<&Vec<TomlTestTarget>>,
     package_root: &Path,
+    edition: Edition,
+    autodiscover: Option<bool>,
+    warnings: &mut Vec<String>,
     errors: &mut Vec<String>,
 ) -> CargoResult<Vec<Target>> {
     let inferred = infer_from_directory(&package_root.join("tests"));
 
-    let targets = clean_targets("test", "test", toml_tests, &inferred, package_root, errors)?;
+    let targets = clean_targets(
+        "test",
+        "test",
+        toml_tests,
+        &inferred,
+        package_root,
+        edition,
+        autodiscover,
+        warnings,
+        errors,
+        "autotests",
+    )?;
 
     let mut result = Vec::new();
     for (path, toml) in targets {
@@ -308,6 +359,8 @@ fn clean_tests(
 fn clean_benches(
     toml_benches: Option<&Vec<TomlBenchTarget>>,
     package_root: &Path,
+    edition: Edition,
+    autodiscover: Option<bool>,
     warnings: &mut Vec<String>,
     errors: &mut Vec<String>,
 ) -> CargoResult<Vec<Target>> {
@@ -336,8 +389,12 @@ fn clean_benches(
             toml_benches,
             &inferred,
             package_root,
+            edition,
+            autodiscover,
+            warnings,
             errors,
             &mut legacy_bench_path,
+            "autobenches",
         )?
     };
 
@@ -359,7 +416,11 @@ fn clean_targets(
     toml_targets: Option<&Vec<TomlTarget>>,
     inferred: &[(String, PathBuf)],
     package_root: &Path,
+    edition: Edition,
+    autodiscover: Option<bool>,
+    warnings: &mut Vec<String>,
     errors: &mut Vec<String>,
+    autodiscover_flag_name: &str,
 ) -> CargoResult<Vec<(PathBuf, TomlTarget)>> {
     clean_targets_with_legacy_path(
         target_kind_human,
@@ -367,8 +428,12 @@ fn clean_targets(
         toml_targets,
         inferred,
         package_root,
+        edition,
+        autodiscover,
+        warnings,
         errors,
         &mut |_| None,
+        autodiscover_flag_name,
     )
 }
 
@@ -378,12 +443,23 @@ fn clean_targets_with_legacy_path(
     toml_targets: Option<&Vec<TomlTarget>>,
     inferred: &[(String, PathBuf)],
     package_root: &Path,
+    edition: Edition,
+    autodiscover: Option<bool>,
+    warnings: &mut Vec<String>,
     errors: &mut Vec<String>,
     legacy_path: &mut FnMut(&TomlTarget) -> Option<PathBuf>,
+    autodiscover_flag_name: &str,
 ) -> CargoResult<Vec<(PathBuf, TomlTarget)>> {
     let toml_targets = toml_targets_and_inferred(
         toml_targets,
         inferred,
+        package_root,
+        autodiscover,
+        edition,
+        warnings,
+        target_kind_human,
+        target_kind,
+        autodiscover_flag_name,
     );
 
     for target in &toml_targets {
@@ -473,10 +549,81 @@ fn is_not_dotfile(entry: &DirEntry) -> bool {
 fn toml_targets_and_inferred(
     toml_targets: Option<&Vec<TomlTarget>>,
     inferred: &[(String, PathBuf)],
+    package_root: &Path,
+    autodiscover: Option<bool>,
+    edition: Edition,
+    warnings: &mut Vec<String>,
+    target_kind_human: &str,
+    target_kind: &str,
+    autodiscover_flag_name: &str,
 ) -> Vec<TomlTarget> {
+    let inferred_targets = inferred_to_toml_targets(inferred);
     match toml_targets {
-        None => inferred_to_toml_targets(inferred),
-        Some(targets) => targets.clone(),
+        None => inferred_targets,
+        Some(targets) => {
+            let mut targets = targets.clone();
+
+            let target_path =
+                |target: &TomlTarget| target.path.clone().map(|p| package_root.join(p.0));
+
+            let mut seen_names = HashSet::new();
+            let mut seen_paths = HashSet::new();
+            for target in targets.iter() {
+                seen_names.insert(target.name.clone());
+                seen_paths.insert(target_path(target));
+            }
+
+            let mut rem_targets = vec![];
+            for target in inferred_targets {
+                if !seen_names.contains(&target.name) && !seen_paths.contains(&target_path(&target))
+                {
+                    rem_targets.push(target);
+                }
+            }
+
+            let autodiscover = match autodiscover {
+                Some(autodiscover) => autodiscover,
+                None => match edition {
+                    Edition::Edition2018 => true,
+                    Edition::Edition2015 => {
+                        if !rem_targets.is_empty() {
+                            let mut rem_targets_str = String::new();
+                            for t in rem_targets.iter() {
+                                if let Some(p) = t.path.clone() {
+                                    rem_targets_str.push_str(&format!("* {:?}\n", p.0))
+                                }
+                            }
+                            warnings.push(format!(
+                                "\
+An explicit [[{section}]] section is specified in Cargo.toml which currently disables Cargo from \
+automatically inferring other {target_kind_human} targets. This inference behavior will change in \
+the Rust 2018 edition and the following files will be included as a {target_kind_human} target:
+
+{rem_targets_str}
+This is likely to break cargo build or cargo test as these files may not be ready to be compiled \
+as a {target_kind_human} target today. You can future-proof yourself and disable this warning by \
+adding {autodiscover_flag_name} = false to your [package] section. You may also move the files to \
+a location where Cargo would not automatically infer them to be a target, such as in subfolders.
+
+For more information on this warning you can consult https://github.com/rust-lang/cargo/issues/5330\
+                            ",
+                                section = target_kind,
+                                target_kind_human = target_kind_human,
+                                rem_targets_str = rem_targets_str,
+                                autodiscover_flag_name = autodiscover_flag_name,
+                            ));
+                        };
+                        false
+                    }
+                },
+            };
+
+            if autodiscover {
+                targets.append(&mut rem_targets);
+            }
+
+            targets
+        }
     }
 }
 
index 334230d422d4c688ae6b1c428a0500db31bcaa0b..0e62242a00de486ab599977df8d165ff7137d9a7 100644 (file)
@@ -728,6 +728,10 @@ proc-macro = false
 harness = true
 ```
 
+The `[package]` also includes the optional `autobins`, `autoexamples`,
+`autotests`, and `autobenches` keys to explicitly opt-in or opt-out of
+auto-discovering specific target kinds.
+
 #### The `required-features` field (optional)
 
 The `required-features` field specifies which features the target needs in order
index 7347795f0b1af39ac15ff69ce1b16396eb01bc0d..770faf9d3d96a343a89a082d121a106ddd5e35f0 100644 (file)
@@ -1,7 +1,7 @@
 use std::str;
 
 use cargo::util::process;
-use cargotest::is_nightly;
+use cargotest::{is_nightly, ChannelChanger};
 use cargotest::support::paths::CargoPathExt;
 use cargotest::support::{basic_bin_manifest, basic_lib_manifest, execs, project};
 use hamcrest::{assert_that, existing_file};
@@ -697,6 +697,83 @@ fn external_bench_implicit() {
     );
 }
 
+#[test]
+fn bench_autodiscover_2015() {
+    if !is_nightly() {
+        return;
+    }
+
+    let p = project("foo")
+        .file(
+            "Cargo.toml",
+            r#"
+            cargo-features = ["edition"]
+
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+            rust = "2015"
+
+            [[bench]]
+            name = "bench_magic"
+            required-features = ["magic"]
+        "#,
+        )
+        .file("src/lib.rs", "")
+        .file(
+            "benches/bench_basic.rs",
+            r#"
+            #![feature(test)]
+            #[allow(unused_extern_crates)]
+            extern crate foo;
+            extern crate test;
+
+            #[bench]
+            fn bench_basic(_b: &mut test::Bencher) {}
+        "#,
+        )
+        .file(
+            "benches/bench_magic.rs",
+            r#"
+            #![feature(test)]
+            #[allow(unused_extern_crates)]
+            extern crate foo;
+            extern crate test;
+
+            #[bench]
+            fn bench_magic(_b: &mut test::Bencher) {}
+        "#,
+        )
+        .build();
+
+    assert_that(
+        p.cargo("bench")
+            .arg("bench_basic")
+            .masquerade_as_nightly_cargo(),
+        execs().with_stderr(&format!(
+            "warning: \
+An explicit [[bench]] section is specified in Cargo.toml which currently disables Cargo from \
+automatically inferring other benchmark targets. This inference behavior will change in \
+the Rust 2018 edition and the following files will be included as a benchmark target:
+
+* \"[..]foo[/]benches[/]bench_basic.rs\"
+
+This is likely to break cargo build or cargo test as these files may not be ready to be compiled \
+as a benchmark target today. You can future-proof yourself and disable this warning by \
+adding autobenches = false to your [package] section. You may also move the files to \
+a location where Cargo would not automatically infer them to be a target, such as in subfolders.
+
+For more information on this warning you can consult https://github.com/rust-lang/cargo/issues/5330
+[COMPILING] foo v0.0.1 ({})
+[FINISHED] release [optimized] target(s) in [..]
+[RUNNING] target[/]release[/]deps[/]foo-[..][EXE]
+",
+            p.url()
+        )),
+    );
+}
+
 #[test]
 fn dont_run_examples() {
     if !is_nightly() {
index 80c1404d915c1a04fc4211062782c5436cae8763..2cb04e1bbbee3900c0dc99df8f4d8c76ad8e77e4 100644 (file)
@@ -2264,7 +2264,6 @@ fn non_existing_example() {
         "#,
         )
         .file("src/lib.rs", "")
-        .file("examples/ehlo.rs", "")
         .build();
 
     assert_that(
index 742f37d04f1f5214aba91aeb1b294ad9525da756..5fdac77c7877ca2332aa56bc7a045018a1b60581 100644 (file)
@@ -1,5 +1,6 @@
 use cargo::util::paths::dylib_path_envvar;
-use cargotest::support::{execs, project, path2url};
+use cargotest::{self, ChannelChanger};
+use cargotest::support::{execs, project, Project, path2url};
 use hamcrest::{assert_that, existing_file};
 
 #[test]
@@ -386,6 +387,151 @@ fn run_example() {
     );
 }
 
+fn autodiscover_examples_project(rust_edition: &str, autoexamples: Option<bool>) -> Project {
+    let autoexamples = match autoexamples {
+        None => "".to_string(),
+        Some(bool) => format!("autoexamples = {}", bool),
+    };
+    project("foo")
+        .file(
+            "Cargo.toml",
+            &format!(
+                r#"
+            cargo-features = ["edition"]
+
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+            rust = "{rust_edition}"
+            {autoexamples}
+
+            [features]
+            magic = []
+
+            [[example]]
+            name = "do_magic"
+            required-features = ["magic"]
+        "#,
+                rust_edition = rust_edition,
+                autoexamples = autoexamples
+            ),
+        )
+        .file(
+            "examples/a.rs",
+            r#"
+            fn main() { println!("example"); }
+        "#,
+        )
+        .file(
+            "examples/do_magic.rs",
+            r#"
+            fn main() { println!("magic example"); }
+        "#,
+        )
+        .build()
+}
+
+#[test]
+fn run_example_autodiscover_2015() {
+    if !cargotest::is_nightly() {
+        return;
+    }
+
+    let p = autodiscover_examples_project("2015", None);
+    assert_that(
+        p.cargo("run")
+            .arg("--example")
+            .arg("a")
+            .masquerade_as_nightly_cargo(),
+        execs().with_status(101).with_stderr(
+            "warning: \
+An explicit [[example]] section is specified in Cargo.toml which currently disables Cargo from \
+automatically inferring other example targets. This inference behavior will change in \
+the Rust 2018 edition and the following files will be included as a example target:
+
+* \"[..]foo[/]examples[/]a.rs\"
+
+This is likely to break cargo build or cargo test as these files may not be ready to be compiled \
+as a example target today. You can future-proof yourself and disable this warning by \
+adding autoexamples = false to your [package] section. You may also move the files to \
+a location where Cargo would not automatically infer them to be a target, such as in subfolders.
+
+For more information on this warning you can consult https://github.com/rust-lang/cargo/issues/5330
+error: no example target named `a`
+",
+        ),
+    );
+}
+
+#[test]
+fn run_example_autodiscover_2015_with_autoexamples_enabled() {
+    if !cargotest::is_nightly() {
+        return;
+    }
+
+    let p = autodiscover_examples_project("2015", Some(true));
+    assert_that(
+        p.cargo("run")
+            .arg("--example")
+            .arg("a")
+            .masquerade_as_nightly_cargo(),
+        execs()
+            .with_status(0)
+            .with_stderr(&format!(
+                "\
+[COMPILING] foo v0.0.1 ({dir})
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target[/]debug[/]examples[/]a[EXE]`",
+                dir = path2url(p.root())
+            ))
+            .with_stdout("example"),
+    );
+}
+
+#[test]
+fn run_example_autodiscover_2015_with_autoexamples_disabled() {
+    if !cargotest::is_nightly() {
+        return;
+    }
+
+    let p = autodiscover_examples_project("2015", Some(false));
+    assert_that(
+        p.cargo("run")
+            .arg("--example")
+            .arg("a")
+            .masquerade_as_nightly_cargo(),
+        execs()
+            .with_status(101)
+            .with_stderr("error: no example target named `a`\n"),
+    );
+}
+
+#[test]
+fn run_example_autodiscover_2018() {
+    if !cargotest::is_nightly() {
+        return;
+    }
+
+    let p = autodiscover_examples_project("2018", None);
+    assert_that(
+        p.cargo("run")
+            .arg("--example")
+            .arg("a")
+            .masquerade_as_nightly_cargo(),
+        execs()
+            .with_status(0)
+            .with_stderr(&format!(
+                "\
+[COMPILING] foo v0.0.1 ({dir})
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target[/]debug[/]examples[/]a[EXE]`",
+                dir = path2url(p.root())
+            ))
+            .with_stdout("example"),
+    );
+}
+
 #[test]
 fn run_bins() {
     let p = project("foo")