diff --git a/src/bin/cargo/command_prelude.rs b/src/bin/cargo/command_prelude.rs index 8cfe5bf0d34..813c5c5d4e3 100644 --- a/src/bin/cargo/command_prelude.rs +++ b/src/bin/cargo/command_prelude.rs @@ -147,8 +147,14 @@ pub trait AppExt: Sized { a global configuration.", ).value_name("VCS") .possible_values(&["git", "hg", "pijul", "fossil", "none"]), - )._arg(opt("bin", "Use a binary (application) template [default]")) + ) + ._arg(opt("bin", "Use a binary (application) template [default]")) ._arg(opt("lib", "Use a library template")) + ._arg( + opt("edition", "Edition to set for the crate generated") + .possible_values(&["2015", "2018"]) + .value_name("YEAR") + ) ._arg( opt( "name", @@ -339,6 +345,7 @@ pub trait ArgMatchesExt { self._is_present("lib"), self.value_of_path("path", config).unwrap(), self._value_of("name").map(|s| s.to_string()), + self._value_of("edition").map(|s| s.to_string()), ) } diff --git a/src/cargo/core/compiler/compilation.rs b/src/cargo/core/compiler/compilation.rs index a8d57e62bc6..39c14f0c7c9 100644 --- a/src/cargo/core/compiler/compilation.rs +++ b/src/cargo/core/compiler/compilation.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; use semver::Version; use lazycell::LazyCell; -use core::{Feature, Package, PackageId, Target, TargetKind}; +use core::{Edition, Package, PackageId, Target, TargetKind}; use util::{self, join_paths, process, CargoResult, Config, ProcessBuilder}; use super::BuildContext; @@ -123,8 +123,7 @@ impl<'cfg> Compilation<'cfg> { /// See `process`. pub fn rustc_process(&self, pkg: &Package, target: &Target) -> CargoResult { let mut p = self.fill_env(self.rustc_process.clone(), pkg, true)?; - let manifest = pkg.manifest(); - if manifest.features().is_enabled(Feature::edition()) { + if target.edition() != Edition::Edition2015 { p.arg(format!("--edition={}", target.edition())); } Ok(p) @@ -133,8 +132,7 @@ impl<'cfg> Compilation<'cfg> { /// See `process`. pub fn rustdoc_process(&self, pkg: &Package, target: &Target) -> CargoResult { let mut p = self.fill_env(process(&*self.config.rustdoc()?), pkg, false)?; - let manifest = pkg.manifest(); - if manifest.features().is_enabled(Feature::edition()) { + if target.edition() != Edition::Edition2015 { p.arg("-Zunstable-options"); p.arg(format!("--edition={}", target.edition())); } diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index ead3e842312..3f75f938c6f 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -85,6 +85,7 @@ impl FromStr for Edition { } } +#[derive(PartialEq)] enum Status { Stable, Unstable, @@ -106,7 +107,7 @@ macro_rules! features { $( pub fn $feature() -> &'static Feature { fn get(features: &Features) -> bool { - features.$feature + stab!($stab) == Status::Stable || features.$feature } static FEAT: Feature = Feature { name: stringify!($feature), @@ -173,7 +174,7 @@ features! { [unstable] alternative_registries: bool, // Using editions - [unstable] edition: bool, + [stable] edition: bool, // Renaming a package in the manifest via the `package` key [unstable] rename_dependency: bool, diff --git a/src/cargo/ops/cargo_new.rs b/src/cargo/ops/cargo_new.rs index 2f5dcd96fea..3351466d681 100644 --- a/src/cargo/ops/cargo_new.rs +++ b/src/cargo/ops/cargo_new.rs @@ -30,6 +30,7 @@ pub struct NewOptions { /// Absolute path to the directory for the new project pub path: PathBuf, pub name: Option, + pub edition: Option, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -65,6 +66,7 @@ struct MkOptions<'a> { name: &'a str, source_files: Vec, bin: bool, + edition: Option<&'a str>, } impl NewOptions { @@ -74,6 +76,7 @@ impl NewOptions { lib: bool, path: PathBuf, name: Option, + edition: Option, ) -> CargoResult { let kind = match (bin, lib) { (true, true) => bail!("can't specify both lib and binary outputs"), @@ -87,6 +90,7 @@ impl NewOptions { kind, path, name, + edition, }; Ok(opts) } @@ -321,6 +325,7 @@ pub fn new(opts: &NewOptions, config: &Config) -> CargoResult<()> { name, source_files: vec![plan_new_source_file(opts.kind.is_bin(), name.to_string())], bin: opts.kind.is_bin(), + edition: opts.edition.as_ref().map(|s| &**s), }; mk(config, &mkopts).chain_err(|| { @@ -397,6 +402,7 @@ pub fn init(opts: &NewOptions, config: &Config) -> CargoResult<()> { name, bin: src_paths_types.iter().any(|x| x.bin), source_files: src_paths_types, + edition: opts.edition.as_ref().map(|s| &**s), }; mk(config, &mkopts).chain_err(|| { @@ -529,12 +535,19 @@ path = {} r#"[package] name = "{}" version = "0.1.0" -authors = [{}] +authors = [{}]{} [dependencies] {}"#, name, toml::Value::String(author), + match opts.edition { + Some(edition) => { + let edition = toml::Value::String(edition.to_string()); + format!("\nedition = {}", edition) + } + None => String::new(), + }, cargotoml_path_specifier ).as_bytes(), )?; diff --git a/src/doc/src/reference/manifest.md b/src/doc/src/reference/manifest.md index b291aeef2f7..341208d4104 100644 --- a/src/doc/src/reference/manifest.md +++ b/src/doc/src/reference/manifest.md @@ -31,6 +31,24 @@ Versioning](http://semver.org/), so make sure you follow some basic rules: traits, fields, types, functions, methods or anything else. * Use version numbers with three numeric parts such as 1.0.0 rather than 1.0. +#### The `edition` field (optional) + +You can opt in to a specific Rust Edition for your package with the +`edition` key in `Cargo.toml`. If you don't specify the edition, it will +default to 2015. + +```toml +[package] +# ... +edition = '2018' +``` + +The `edition` key affects which edition your package is compiled with. Cargo +will always generate projects via `cargo new` with the `edition` key set to the +latest edition. Setting the `edition` key in `[package]` will affect all +targets/crates in the package, including test suites, benchmarks, binaries, +examples, etc. + #### The `build` field (optional) This field specifies a file in the project root which is a [build script][1] for @@ -714,6 +732,12 @@ proc-macro = false # stops it from generating a test harness. This is useful when the binary being # built manages the test runner itself. harness = true + +# If set then a target can be configured to use a different edition than the +# `[package]` is configured to use, perhaps only compiling a library with the +# 2018 edition or only compiling one unit test with the 2015 edition. By default +# all targets are compiled with the edition specified in `[package]`. +edition = '2015' ``` The `[package]` also includes the optional `autobins`, `autoexamples`, diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 31bde2e9237..96e6202af0d 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -199,30 +199,6 @@ cargo +nightly build --out-dir=out -Z unstable-options ``` -### Edition -* Tracking Issue: [rust-lang/rust#44581](https://github.com/rust-lang/rust/issues/44581) -* RFC: [#2052](https://github.com/rust-lang/rfcs/blob/master/text/2052-epochs.md) - -You can opt in to a specific Rust Edition for your package with the `edition` -key in `Cargo.toml`. If you don't specify the edition, it will default to -2015. You need to include the appropriate `cargo-features`. - -You can also specify `edition` on a per-target level, where it will otherwise -default to the package `edition`. - -```toml -cargo-features = ["edition"] - -[package] -... -edition = "2018" - -[[bin]] -... -edition = "2015" -``` - - ### Profile Overrides * Tracking Issue: [rust-lang/rust#48683](https://github.com/rust-lang/rust/issues/48683) * RFC: [#2282](https://github.com/rust-lang/rfcs/blob/master/text/2282-profile-dependencies.md) diff --git a/tests/testsuite/bench.rs b/tests/testsuite/bench.rs index b7009f6d18b..8d233bb16e5 100644 --- a/tests/testsuite/bench.rs +++ b/tests/testsuite/bench.rs @@ -549,45 +549,42 @@ fn bench_autodiscover_2015() { .file( "Cargo.toml", r#" - cargo-features = ["edition"] - - [project] - name = "foo" - version = "0.0.1" - authors = [] - edition = "2015" - - [[bench]] - name = "bench_magic" - required-features = ["magic"] - "#, + [project] + name = "foo" + version = "0.0.1" + authors = [] + edition = "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; + #![feature(test)] + #[allow(unused_extern_crates)] + extern crate foo; + extern crate test; - #[bench] - fn bench_basic(_b: &mut test::Bencher) {} - "#, + #[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; + #![feature(test)] + #[allow(unused_extern_crates)] + extern crate foo; + extern crate test; - #[bench] - fn bench_magic(_b: &mut test::Bencher) {} - "#, + #[bench] + fn bench_magic(_b: &mut test::Bencher) {} + "#, ).build(); p.cargo("bench bench_basic") - .masquerade_as_nightly_cargo() .with_stderr( "warning: \ An explicit [[bench]] section is specified in Cargo.toml which currently diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index 7c0d4245a5e..dcfd95b4897 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -3928,19 +3928,17 @@ fn target_edition() { .file( "Cargo.toml", r#" - cargo-features = ["edition"] - [package] - name = "foo" - version = "0.0.1" + [package] + name = "foo" + version = "0.0.1" - [lib] - edition = "2018" - "#, + [lib] + edition = "2018" + "#, ).file("src/lib.rs", "") .build(); p.cargo("build -v") - .masquerade_as_nightly_cargo() .with_stderr_contains( "\ [COMPILING] foo v0.0.1 ([..]) @@ -3955,62 +3953,26 @@ fn target_edition_override() { .file( "Cargo.toml", r#" - cargo-features = ["edition"] - [package] - name = "foo" - version = "0.0.1" - authors = [] - edition = "2018" - - [lib] - edition = "2015" - "#, - ).file("src/lib.rs", "") - .build(); - - p.cargo("build -v") - .masquerade_as_nightly_cargo() - .with_stderr_contains( - "\ -[COMPILING] foo v0.0.1 ([..]) -[RUNNING] `rustc [..]--edition=2015 [..] -", - ).run(); -} - -#[test] -fn target_edition_feature_gated() { - let p = project() - .file( - "Cargo.toml", - r#" - [package] - name = "foo" - version = "0.0.1" - authors = [] + [package] + name = "foo" + version = "0.0.1" + authors = [] + edition = "2018" - [lib] - edition = "2018" - "#, - ).file("src/lib.rs", "") + [lib] + edition = "2015" + "#, + ).file( + "src/lib.rs", + " + pub fn async() {} + pub fn try() {} + pub fn await() {} + " + ) .build(); - p.cargo("build -v") - .masquerade_as_nightly_cargo() - .with_status(101) - .with_stderr( - "\ -error: failed to parse manifest at `[..]` - -Caused by: - editions are unstable - -Caused by: - feature `edition` is required - -consider adding `cargo-features = [\"edition\"]` to the manifest -", - ).run(); + p.cargo("build -v").run(); } #[test] diff --git a/tests/testsuite/edition.rs b/tests/testsuite/edition.rs index 972c8249178..8b35c6ccffd 100644 --- a/tests/testsuite/edition.rs +++ b/tests/testsuite/edition.rs @@ -10,7 +10,6 @@ fn edition_works_for_build_script() { .file( "Cargo.toml", r#" - cargo-features = ['edition'] [package] name = 'foo' version = '0.1.0' diff --git a/tests/testsuite/fix.rs b/tests/testsuite/fix.rs index 06b21399052..01d64c8095e 100644 --- a/tests/testsuite/fix.rs +++ b/tests/testsuite/fix.rs @@ -355,8 +355,6 @@ fn upgrade_extern_crate() { .file( "Cargo.toml", r#" - cargo-features = ["edition"] - [package] name = "foo" version = "0.1.0" @@ -392,7 +390,6 @@ fn upgrade_extern_crate() { "; p.cargo("fix --allow-no-vcs") .env("__CARGO_FIX_YOLO", "1") - .masquerade_as_nightly_cargo() .with_stderr(stderr) .with_stdout("") .run(); @@ -830,8 +827,6 @@ fn prepare_for_and_enable() { .file( "Cargo.toml", r#" - cargo-features = ['edition'] - [package] name = 'foo' version = '0.1.0' @@ -853,7 +848,6 @@ information about transitioning to the 2018 edition see: "; p.cargo("fix --edition --allow-no-vcs") - .masquerade_as_nightly_cargo() .with_stderr_contains(stderr) .with_status(101) .run(); @@ -925,7 +919,6 @@ fn fix_idioms() { .file( "Cargo.toml", r#" - cargo-features = ['edition'] [package] name = 'foo' version = '0.1.0' @@ -947,7 +940,6 @@ fn fix_idioms() { [FINISHED] [..] "; p.cargo("fix --edition-idioms --allow-no-vcs") - .masquerade_as_nightly_cargo() .with_stderr(stderr) .with_status(0) .run(); diff --git a/tests/testsuite/new.rs b/tests/testsuite/new.rs index 44c0b142a14..7d524544b30 100644 --- a/tests/testsuite/new.rs +++ b/tests/testsuite/new.rs @@ -455,3 +455,30 @@ fn explicit_project_name() { .with_stderr("[CREATED] library `bar` project") .run(); } + +#[test] +fn new_with_edition_2015() { + cargo_process("new --edition 2015 foo") + .env("USER", "foo") + .run(); + let manifest = fs::read_to_string(paths::root().join("foo/Cargo.toml")).unwrap(); + assert!(manifest.contains("edition = \"2015\"")); +} + +#[test] +fn new_with_edition_2018() { + cargo_process("new --edition 2018 foo") + .env("USER", "foo") + .run(); + let manifest = fs::read_to_string(paths::root().join("foo/Cargo.toml")).unwrap(); + assert!(manifest.contains("edition = \"2018\"")); +} + +#[test] +fn new_with_bad_edition() { + cargo_process("new --edition something_else foo") + .env("USER", "foo") + .with_stderr_contains("error: 'something_else' isn't a valid value[..]") + .with_status(1) + .run(); +} diff --git a/tests/testsuite/package.rs b/tests/testsuite/package.rs index 3a8503c7d06..a32ca89b9db 100644 --- a/tests/testsuite/package.rs +++ b/tests/testsuite/package.rs @@ -973,45 +973,19 @@ fn edition_with_metadata() { .file( "Cargo.toml", r#" - cargo-features = ["edition"] - [package] - name = "foo" - version = "0.0.1" - authors = [] - edition = "2018" - [package.metadata.docs.rs] - features = ["foobar"] - "#, + [package] + name = "foo" + version = "0.0.1" + authors = [] + edition = "2018" + + [package.metadata.docs.rs] + features = ["foobar"] + "#, ).file("src/lib.rs", "") .build(); - p.cargo("package").masquerade_as_nightly_cargo().run(); -} - -#[test] -fn test_edition_missing() { - // no edition = 2015 - let p = project() - .file( - "Cargo.toml", - r#" - cargo-features = ["edition"] - [package] - name = "foo" - version = "0.0.1" - authors = [] - "#, - ).file("src/lib.rs", r#" "#) - .build(); - - p.cargo("build -v").masquerade_as_nightly_cargo() - // --edition is still in flux and we're not passing -Zunstable-options - // from Cargo so it will probably error. Only partially match the output - // until stuff stabilizes - .with_stderr_contains("\ -[COMPILING] foo v0.0.1 ([..]) -[RUNNING] `rustc [..]--edition=2015 [..] -").run(); + p.cargo("package").run(); } #[test] @@ -1020,18 +994,16 @@ fn test_edition_malformed() { .file( "Cargo.toml", r#" - cargo-features = ["edition"] - [package] - name = "foo" - version = "0.0.1" - authors = [] - edition = "chicken" - "#, + [package] + name = "foo" + version = "0.0.1" + authors = [] + edition = "chicken" + "#, ).file("src/lib.rs", r#" "#) .build(); p.cargo("build -v") - .masquerade_as_nightly_cargo() .with_status(101) .with_stderr( "\ @@ -1046,39 +1018,6 @@ Caused by: ).run(); } -#[test] -fn test_edition_nightly() { - let p = project() - .file( - "Cargo.toml", - r#" - [package] - name = "foo" - version = "0.0.1" - authors = [] - edition = "2015" - "#, - ).file("src/lib.rs", r#" "#) - .build(); - - p.cargo("build -v") - .masquerade_as_nightly_cargo() - .with_status(101) - .with_stderr( - "\ -error: failed to parse manifest at `[..]` - -Caused by: - editions are unstable - -Caused by: - feature `edition` is required - -consider adding `cargo-features = [\"edition\"]` to the manifest -", - ).run(); -} - #[test] fn package_lockfile() { let p = project() diff --git a/tests/testsuite/run.rs b/tests/testsuite/run.rs index ea66fdf702b..344d03ea184 100644 --- a/tests/testsuite/run.rs +++ b/tests/testsuite/run.rs @@ -359,22 +359,20 @@ fn autodiscover_examples_project(rust_edition: &str, autoexamples: Option) "Cargo.toml", &format!( r#" - cargo-features = ["edition"] - - [project] - name = "foo" - version = "0.0.1" - authors = [] - edition = "{rust_edition}" - {autoexamples} - - [features] - magic = [] - - [[example]] - name = "do_magic" - required-features = ["magic"] - "#, + [project] + name = "foo" + version = "0.0.1" + authors = [] + edition = "{rust_edition}" + {autoexamples} + + [features] + magic = [] + + [[example]] + name = "do_magic" + required-features = ["magic"] + "#, rust_edition = rust_edition, autoexamples = autoexamples ), @@ -382,8 +380,8 @@ fn autodiscover_examples_project(rust_edition: &str, autoexamples: Option) .file( "examples/do_magic.rs", r#" - fn main() { println!("magic example"); } - "#, + fn main() { println!("magic example"); } + "#, ).build() } @@ -395,7 +393,6 @@ fn run_example_autodiscover_2015() { let p = autodiscover_examples_project("2015", None); p.cargo("run --example a") - .masquerade_as_nightly_cargo() .with_status(101) .with_stderr( "warning: \ @@ -427,7 +424,6 @@ fn run_example_autodiscover_2015_with_autoexamples_enabled() { let p = autodiscover_examples_project("2015", Some(true)); p.cargo("run --example a") - .masquerade_as_nightly_cargo() .with_stderr( "\ [COMPILING] foo v0.0.1 (CWD) @@ -445,7 +441,6 @@ fn run_example_autodiscover_2015_with_autoexamples_disabled() { let p = autodiscover_examples_project("2015", Some(false)); p.cargo("run --example a") - .masquerade_as_nightly_cargo() .with_status(101) .with_stderr("error: no example target named `a`\n") .run(); @@ -459,7 +454,6 @@ fn run_example_autodiscover_2018() { let p = autodiscover_examples_project("2018", None); p.cargo("run --example a") - .masquerade_as_nightly_cargo() .with_stderr( "\ [COMPILING] foo v0.0.1 (CWD)