diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 7f20e979b4f..c1daaf9091d 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -459,6 +459,10 @@ pub struct TomlProject { workspace: Option, #[serde(rename = "im-a-teapot")] im_a_teapot: Option, + autobins: Option, + autoexamples: Option, + autotests: Option, + autobenches: Option, // package metadata description: Option, @@ -633,6 +637,19 @@ impl TomlManifest { let pkgid = project.to_package_id(source_id)?; + let edition = if let Some(ref edition) = project.rust { + features + .require(Feature::edition()) + .chain_err(|| "editions are unstable")?; + if let Ok(edition) = edition.parse() { + edition + } else { + bail!("the `rust` key must be one of: `2015`, `2018`") + } + } else { + Edition::Edition2015 + }; + // If we have no lib at all, use the inferred lib if available // If we have a lib with a path, we're done // If we have a lib with no path, use the inferred lib or_else package name @@ -640,6 +657,7 @@ impl TomlManifest { me, package_name, package_root, + edition, &project.build, &mut warnings, &mut errors, @@ -798,18 +816,6 @@ impl TomlManifest { None => false, }; - let edition = if let Some(ref edition) = project.rust { - features - .require(Feature::edition()) - .chain_err(|| "editiones are unstable")?; - if let Ok(edition) = edition.parse() { - edition - } else { - bail!("the `rust` key must be one of: `2015`, `2018`") - } - } else { - Edition::Edition2015 - }; let custom_metadata = project.metadata.clone(); let mut manifest = Manifest::new( summary, diff --git a/src/cargo/util/toml/targets.rs b/src/cargo/util/toml/targets.rs index 896278c49c2..9e3c8a0af59 100644 --- a/src/cargo/util/toml/targets.rs +++ b/src/cargo/util/toml/targets.rs @@ -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, warnings: &mut Vec, errors: &mut Vec, @@ -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,22 +184,25 @@ fn clean_bins( toml_bins: Option<&Vec>, package_root: &Path, package_name: &str, + edition: Edition, + autodiscover: Option, warnings: &mut Vec, errors: &mut Vec, has_lib: bool, ) -> CargoResult> { let inferred = inferred_bins(package_root, package_name); - let bins = match toml_bins { - Some(bins) => bins.clone(), - None => inferred - .iter() - .map(|&(ref name, ref path)| TomlTarget { - name: Some(name.clone()), - path: Some(PathValue(path.clone())), - ..TomlTarget::new() - }) - .collect(), - }; + + let bins = toml_targets_and_inferred( + toml_bins, + &inferred, + package_root, + autodiscover, + edition, + warnings, + "binary", + "bin", + "autobins", + ); for bin in &bins { validate_has_name(bin, "binary", "bin")?; @@ -260,6 +284,9 @@ fn clean_bins( fn clean_examples( toml_examples: Option<&Vec>, package_root: &Path, + edition: Edition, + autodiscover: Option, + warnings: &mut Vec, errors: &mut Vec, ) -> CargoResult> { let inferred = infer_from_directory(&package_root.join("examples")); @@ -270,7 +297,11 @@ fn clean_examples( toml_examples, &inferred, package_root, + edition, + autodiscover, + warnings, errors, + "autoexamples", )?; let mut result = Vec::new(); @@ -296,11 +327,25 @@ fn clean_examples( fn clean_tests( toml_tests: Option<&Vec>, package_root: &Path, + edition: Edition, + autodiscover: Option, + warnings: &mut Vec, errors: &mut Vec, ) -> CargoResult> { 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 { @@ -314,34 +359,46 @@ fn clean_tests( fn clean_benches( toml_benches: Option<&Vec>, package_root: &Path, + edition: Edition, + autodiscover: Option, warnings: &mut Vec, errors: &mut Vec, ) -> CargoResult> { - let mut legacy_bench_path = |bench: &TomlTarget| { - let legacy_path = package_root.join("src").join("bench.rs"); - if !(bench.name() == "bench" && legacy_path.exists()) { - return None; - } - warnings.push(format!( - "path `{}` was erroneously implicitly accepted for benchmark `{}`,\n\ - please set bench.path in Cargo.toml", - legacy_path.display(), - bench.name() - )); - Some(legacy_path) - }; + let mut legacy_warnings = vec![]; + + let targets = { + let mut legacy_bench_path = |bench: &TomlTarget| { + let legacy_path = package_root.join("src").join("bench.rs"); + if !(bench.name() == "bench" && legacy_path.exists()) { + return None; + } + legacy_warnings.push(format!( + "path `{}` was erroneously implicitly accepted for benchmark `{}`,\n\ + please set bench.path in Cargo.toml", + legacy_path.display(), + bench.name() + )); + Some(legacy_path) + }; - let inferred = infer_from_directory(&package_root.join("benches")); + let inferred = infer_from_directory(&package_root.join("benches")); + + clean_targets_with_legacy_path( + "benchmark", + "bench", + toml_benches, + &inferred, + package_root, + edition, + autodiscover, + warnings, + errors, + &mut legacy_bench_path, + "autobenches", + )? + }; - let targets = clean_targets_with_legacy_path( - "benchmark", - "bench", - toml_benches, - &inferred, - package_root, - errors, - &mut legacy_bench_path, - )?; + warnings.append(&mut legacy_warnings); let mut result = Vec::new(); for (path, toml) in targets { @@ -359,7 +416,11 @@ fn clean_targets( toml_targets: Option<&Vec>, inferred: &[(String, PathBuf)], package_root: &Path, + edition: Edition, + autodiscover: Option, + warnings: &mut Vec, errors: &mut Vec, + autodiscover_flag_name: &str, ) -> CargoResult> { 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,20 +443,24 @@ fn clean_targets_with_legacy_path( toml_targets: Option<&Vec>, inferred: &[(String, PathBuf)], package_root: &Path, + edition: Edition, + autodiscover: Option, + warnings: &mut Vec, errors: &mut Vec, legacy_path: &mut FnMut(&TomlTarget) -> Option, + autodiscover_flag_name: &str, ) -> CargoResult> { - let toml_targets = match toml_targets { - Some(targets) => targets.clone(), - None => inferred - .iter() - .map(|&(ref name, ref path)| TomlTarget { - name: Some(name.clone()), - path: Some(PathValue(path.clone())), - ..TomlTarget::new() - }) - .collect(), - }; + 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 { validate_has_name(target, target_kind_human, target_kind)?; @@ -477,6 +546,100 @@ fn is_not_dotfile(entry: &DirEntry) -> bool { entry.file_name().to_str().map(|s| s.starts_with('.')) == Some(false) } +fn toml_targets_and_inferred( + toml_targets: Option<&Vec>, + inferred: &[(String, PathBuf)], + package_root: &Path, + autodiscover: Option, + edition: Edition, + warnings: &mut Vec, + target_kind_human: &str, + target_kind: &str, + autodiscover_flag_name: &str, +) -> Vec { + let inferred_targets = inferred_to_toml_targets(inferred); + match toml_targets { + 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.display())) + } + } + 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 + } + } +} + +fn inferred_to_toml_targets(inferred: &[(String, PathBuf)]) -> Vec { + inferred + .iter() + .map(|&(ref name, ref path)| TomlTarget { + name: Some(name.clone()), + path: Some(PathValue(path.clone())), + ..TomlTarget::new() + }) + .collect() +} + fn validate_has_name( target: &TomlTarget, target_kind_human: &str, diff --git a/src/doc/src/reference/manifest.md b/src/doc/src/reference/manifest.md index 334230d422d..0e62242a00d 100644 --- a/src/doc/src/reference/manifest.md +++ b/src/doc/src/reference/manifest.md @@ -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 diff --git a/tests/testsuite/bench.rs b/tests/testsuite/bench.rs index 7347795f0b1..595b47926d0 100644 --- a/tests/testsuite/bench.rs +++ b/tests/testsuite/bench.rs @@ -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,86 @@ 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: + +* [..]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() { diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index 80c1404d915..2cb04e1bbbe 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -2264,7 +2264,6 @@ fn non_existing_example() { "#, ) .file("src/lib.rs", "") - .file("examples/ehlo.rs", "") .build(); assert_that( diff --git a/tests/testsuite/package.rs b/tests/testsuite/package.rs index 20e6821d3bb..0a9845a20d6 100644 --- a/tests/testsuite/package.rs +++ b/tests/testsuite/package.rs @@ -1220,7 +1220,7 @@ fn test_edition_nightly() { error: failed to parse manifest at `[..]` Caused by: - editiones are unstable + editions are unstable Caused by: feature `edition` is required diff --git a/tests/testsuite/run.rs b/tests/testsuite/run.rs index 742f37d04f1..47360c4cd5f 100644 --- a/tests/testsuite/run.rs +++ b/tests/testsuite/run.rs @@ -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,154 @@ fn run_example() { ); } +fn autodiscover_examples_project(rust_edition: &str, autoexamples: Option) -> 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: + +* [..]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")