Auto merge of #5984 - alexcrichton:stabilize-edition, r=ehuss

Stabilize `edition` key and add `cargo new --edition`

This commit stabilizes the `edition` key in `Cargo.toml`, both in the
`[package]` section and inside subtargets. Additionally the `cargo new` and
`cargo init` subcommands have been enhanced with a `--edition` flag to allow
explicitly specifying the edition to be generated.

This commit does not yet change the default edition that's generated.

Closes #5980
This commit is contained in:
bors 2018-09-06 22:17:24 +00:00
commit 56ee6204de
13 changed files with 155 additions and 226 deletions

View File

@ -147,8 +147,14 @@ pub trait AppExt: Sized {
a global configuration.", a global configuration.",
).value_name("VCS") ).value_name("VCS")
.possible_values(&["git", "hg", "pijul", "fossil", "none"]), .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("lib", "Use a library template"))
._arg(
opt("edition", "Edition to set for the crate generated")
.possible_values(&["2015", "2018"])
.value_name("YEAR")
)
._arg( ._arg(
opt( opt(
"name", "name",
@ -339,6 +345,7 @@ pub trait ArgMatchesExt {
self._is_present("lib"), self._is_present("lib"),
self.value_of_path("path", config).unwrap(), self.value_of_path("path", config).unwrap(),
self._value_of("name").map(|s| s.to_string()), self._value_of("name").map(|s| s.to_string()),
self._value_of("edition").map(|s| s.to_string()),
) )
} }

View File

@ -6,7 +6,7 @@ use std::path::PathBuf;
use semver::Version; use semver::Version;
use lazycell::LazyCell; 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 util::{self, join_paths, process, CargoResult, Config, ProcessBuilder};
use super::BuildContext; use super::BuildContext;
@ -131,8 +131,7 @@ impl<'cfg> Compilation<'cfg> {
/// See `process`. /// See `process`.
pub fn rustc_process(&self, pkg: &Package, target: &Target) -> CargoResult<ProcessBuilder> { pub fn rustc_process(&self, pkg: &Package, target: &Target) -> CargoResult<ProcessBuilder> {
let mut p = self.fill_env(self.rustc_process.clone(), pkg, true)?; let mut p = self.fill_env(self.rustc_process.clone(), pkg, true)?;
let manifest = pkg.manifest(); if target.edition() != Edition::Edition2015 {
if manifest.features().is_enabled(Feature::edition()) {
p.arg(format!("--edition={}", target.edition())); p.arg(format!("--edition={}", target.edition()));
} }
Ok(p) Ok(p)
@ -141,8 +140,7 @@ impl<'cfg> Compilation<'cfg> {
/// See `process`. /// See `process`.
pub fn rustdoc_process(&self, pkg: &Package, target: &Target) -> CargoResult<ProcessBuilder> { pub fn rustdoc_process(&self, pkg: &Package, target: &Target) -> CargoResult<ProcessBuilder> {
let mut p = self.fill_env(process(&*self.config.rustdoc()?), pkg, false)?; let mut p = self.fill_env(process(&*self.config.rustdoc()?), pkg, false)?;
let manifest = pkg.manifest(); if target.edition() != Edition::Edition2015 {
if manifest.features().is_enabled(Feature::edition()) {
p.arg("-Zunstable-options"); p.arg("-Zunstable-options");
p.arg(format!("--edition={}", target.edition())); p.arg(format!("--edition={}", target.edition()));
} }

View File

@ -85,6 +85,7 @@ impl FromStr for Edition {
} }
} }
#[derive(PartialEq)]
enum Status { enum Status {
Stable, Stable,
Unstable, Unstable,
@ -106,7 +107,7 @@ macro_rules! features {
$( $(
pub fn $feature() -> &'static Feature { pub fn $feature() -> &'static Feature {
fn get(features: &Features) -> bool { fn get(features: &Features) -> bool {
features.$feature stab!($stab) == Status::Stable || features.$feature
} }
static FEAT: Feature = Feature { static FEAT: Feature = Feature {
name: stringify!($feature), name: stringify!($feature),
@ -173,7 +174,7 @@ features! {
[unstable] alternative_registries: bool, [unstable] alternative_registries: bool,
// Using editions // Using editions
[unstable] edition: bool, [stable] edition: bool,
// Renaming a package in the manifest via the `package` key // Renaming a package in the manifest via the `package` key
[unstable] rename_dependency: bool, [unstable] rename_dependency: bool,

View File

@ -30,6 +30,7 @@ pub struct NewOptions {
/// Absolute path to the directory for the new project /// Absolute path to the directory for the new project
pub path: PathBuf, pub path: PathBuf,
pub name: Option<String>, pub name: Option<String>,
pub edition: Option<String>,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -65,6 +66,7 @@ struct MkOptions<'a> {
name: &'a str, name: &'a str,
source_files: Vec<SourceFileInformation>, source_files: Vec<SourceFileInformation>,
bin: bool, bin: bool,
edition: Option<&'a str>,
} }
impl NewOptions { impl NewOptions {
@ -74,6 +76,7 @@ impl NewOptions {
lib: bool, lib: bool,
path: PathBuf, path: PathBuf,
name: Option<String>, name: Option<String>,
edition: Option<String>,
) -> CargoResult<NewOptions> { ) -> CargoResult<NewOptions> {
let kind = match (bin, lib) { let kind = match (bin, lib) {
(true, true) => bail!("can't specify both lib and binary outputs"), (true, true) => bail!("can't specify both lib and binary outputs"),
@ -87,6 +90,7 @@ impl NewOptions {
kind, kind,
path, path,
name, name,
edition,
}; };
Ok(opts) Ok(opts)
} }
@ -321,6 +325,7 @@ pub fn new(opts: &NewOptions, config: &Config) -> CargoResult<()> {
name, name,
source_files: vec![plan_new_source_file(opts.kind.is_bin(), name.to_string())], source_files: vec![plan_new_source_file(opts.kind.is_bin(), name.to_string())],
bin: opts.kind.is_bin(), bin: opts.kind.is_bin(),
edition: opts.edition.as_ref().map(|s| &**s),
}; };
mk(config, &mkopts).chain_err(|| { mk(config, &mkopts).chain_err(|| {
@ -397,6 +402,7 @@ pub fn init(opts: &NewOptions, config: &Config) -> CargoResult<()> {
name, name,
bin: src_paths_types.iter().any(|x| x.bin), bin: src_paths_types.iter().any(|x| x.bin),
source_files: src_paths_types, source_files: src_paths_types,
edition: opts.edition.as_ref().map(|s| &**s),
}; };
mk(config, &mkopts).chain_err(|| { mk(config, &mkopts).chain_err(|| {
@ -529,12 +535,19 @@ path = {}
r#"[package] r#"[package]
name = "{}" name = "{}"
version = "0.1.0" version = "0.1.0"
authors = [{}] authors = [{}]{}
[dependencies] [dependencies]
{}"#, {}"#,
name, name,
toml::Value::String(author), 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 cargotoml_path_specifier
).as_bytes(), ).as_bytes(),
)?; )?;

View File

@ -31,6 +31,24 @@ Versioning](http://semver.org/), so make sure you follow some basic rules:
traits, fields, types, functions, methods or anything else. traits, fields, types, functions, methods or anything else.
* Use version numbers with three numeric parts such as 1.0.0 rather than 1.0. * 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) #### The `build` field (optional)
This field specifies a file in the project root which is a [build script][1] for 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 # stops it from generating a test harness. This is useful when the binary being
# built manages the test runner itself. # built manages the test runner itself.
harness = true 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`, The `[package]` also includes the optional `autobins`, `autoexamples`,

View File

@ -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 ### Profile Overrides
* Tracking Issue: [rust-lang/rust#48683](https://github.com/rust-lang/rust/issues/48683) * 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) * RFC: [#2282](https://github.com/rust-lang/rfcs/blob/master/text/2282-profile-dependencies.md)

View File

@ -549,8 +549,6 @@ fn bench_autodiscover_2015() {
.file( .file(
"Cargo.toml", "Cargo.toml",
r#" r#"
cargo-features = ["edition"]
[project] [project]
name = "foo" name = "foo"
version = "0.0.1" version = "0.0.1"
@ -587,7 +585,6 @@ fn bench_autodiscover_2015() {
).build(); ).build();
p.cargo("bench bench_basic") p.cargo("bench bench_basic")
.masquerade_as_nightly_cargo()
.with_stderr( .with_stderr(
"warning: \ "warning: \
An explicit [[bench]] section is specified in Cargo.toml which currently An explicit [[bench]] section is specified in Cargo.toml which currently

View File

@ -3928,7 +3928,6 @@ fn target_edition() {
.file( .file(
"Cargo.toml", "Cargo.toml",
r#" r#"
cargo-features = ["edition"]
[package] [package]
name = "foo" name = "foo"
version = "0.0.1" version = "0.0.1"
@ -3940,7 +3939,6 @@ fn target_edition() {
.build(); .build();
p.cargo("build -v") p.cargo("build -v")
.masquerade_as_nightly_cargo()
.with_stderr_contains( .with_stderr_contains(
"\ "\
[COMPILING] foo v0.0.1 ([..]) [COMPILING] foo v0.0.1 ([..])
@ -3955,7 +3953,6 @@ fn target_edition_override() {
.file( .file(
"Cargo.toml", "Cargo.toml",
r#" r#"
cargo-features = ["edition"]
[package] [package]
name = "foo" name = "foo"
version = "0.0.1" version = "0.0.1"
@ -3965,52 +3962,17 @@ fn target_edition_override() {
[lib] [lib]
edition = "2015" edition = "2015"
"#, "#,
).file("src/lib.rs", "") ).file(
"src/lib.rs",
"
pub fn async() {}
pub fn try() {}
pub fn await() {}
"
)
.build(); .build();
p.cargo("build -v") p.cargo("build -v").run();
.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 = []
[lib]
edition = "2018"
"#,
).file("src/lib.rs", "")
.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] #[test]

View File

@ -10,7 +10,6 @@ fn edition_works_for_build_script() {
.file( .file(
"Cargo.toml", "Cargo.toml",
r#" r#"
cargo-features = ['edition']
[package] [package]
name = 'foo' name = 'foo'
version = '0.1.0' version = '0.1.0'

View File

@ -355,8 +355,6 @@ fn upgrade_extern_crate() {
.file( .file(
"Cargo.toml", "Cargo.toml",
r#" r#"
cargo-features = ["edition"]
[package] [package]
name = "foo" name = "foo"
version = "0.1.0" version = "0.1.0"
@ -392,7 +390,6 @@ fn upgrade_extern_crate() {
"; ";
p.cargo("fix --allow-no-vcs") p.cargo("fix --allow-no-vcs")
.env("__CARGO_FIX_YOLO", "1") .env("__CARGO_FIX_YOLO", "1")
.masquerade_as_nightly_cargo()
.with_stderr(stderr) .with_stderr(stderr)
.with_stdout("") .with_stdout("")
.run(); .run();
@ -830,8 +827,6 @@ fn prepare_for_and_enable() {
.file( .file(
"Cargo.toml", "Cargo.toml",
r#" r#"
cargo-features = ['edition']
[package] [package]
name = 'foo' name = 'foo'
version = '0.1.0' version = '0.1.0'
@ -853,7 +848,6 @@ information about transitioning to the 2018 edition see:
"; ";
p.cargo("fix --edition --allow-no-vcs") p.cargo("fix --edition --allow-no-vcs")
.masquerade_as_nightly_cargo()
.with_stderr_contains(stderr) .with_stderr_contains(stderr)
.with_status(101) .with_status(101)
.run(); .run();
@ -925,7 +919,6 @@ fn fix_idioms() {
.file( .file(
"Cargo.toml", "Cargo.toml",
r#" r#"
cargo-features = ['edition']
[package] [package]
name = 'foo' name = 'foo'
version = '0.1.0' version = '0.1.0'
@ -947,7 +940,6 @@ fn fix_idioms() {
[FINISHED] [..] [FINISHED] [..]
"; ";
p.cargo("fix --edition-idioms --allow-no-vcs") p.cargo("fix --edition-idioms --allow-no-vcs")
.masquerade_as_nightly_cargo()
.with_stderr(stderr) .with_stderr(stderr)
.with_status(0) .with_status(0)
.run(); .run();

View File

@ -455,3 +455,30 @@ fn explicit_project_name() {
.with_stderr("[CREATED] library `bar` project") .with_stderr("[CREATED] library `bar` project")
.run(); .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();
}

View File

@ -971,45 +971,19 @@ fn edition_with_metadata() {
.file( .file(
"Cargo.toml", "Cargo.toml",
r#" r#"
cargo-features = ["edition"]
[package] [package]
name = "foo" name = "foo"
version = "0.0.1" version = "0.0.1"
authors = [] authors = []
edition = "2018" edition = "2018"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["foobar"] features = ["foobar"]
"#, "#,
).file("src/lib.rs", "") ).file("src/lib.rs", "")
.build(); .build();
p.cargo("package").masquerade_as_nightly_cargo().run(); p.cargo("package").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();
} }
#[test] #[test]
@ -1018,7 +992,6 @@ fn test_edition_malformed() {
.file( .file(
"Cargo.toml", "Cargo.toml",
r#" r#"
cargo-features = ["edition"]
[package] [package]
name = "foo" name = "foo"
version = "0.0.1" version = "0.0.1"
@ -1029,7 +1002,6 @@ fn test_edition_malformed() {
.build(); .build();
p.cargo("build -v") p.cargo("build -v")
.masquerade_as_nightly_cargo()
.with_status(101) .with_status(101)
.with_stderr( .with_stderr(
"\ "\
@ -1044,39 +1016,6 @@ Caused by:
).run(); ).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] #[test]
fn package_lockfile() { fn package_lockfile() {
let p = project() let p = project()

View File

@ -359,8 +359,6 @@ fn autodiscover_examples_project(rust_edition: &str, autoexamples: Option<bool>)
"Cargo.toml", "Cargo.toml",
&format!( &format!(
r#" r#"
cargo-features = ["edition"]
[project] [project]
name = "foo" name = "foo"
version = "0.0.1" version = "0.0.1"
@ -395,7 +393,6 @@ fn run_example_autodiscover_2015() {
let p = autodiscover_examples_project("2015", None); let p = autodiscover_examples_project("2015", None);
p.cargo("run --example a") p.cargo("run --example a")
.masquerade_as_nightly_cargo()
.with_status(101) .with_status(101)
.with_stderr( .with_stderr(
"warning: \ "warning: \
@ -427,7 +424,6 @@ fn run_example_autodiscover_2015_with_autoexamples_enabled() {
let p = autodiscover_examples_project("2015", Some(true)); let p = autodiscover_examples_project("2015", Some(true));
p.cargo("run --example a") p.cargo("run --example a")
.masquerade_as_nightly_cargo()
.with_stderr( .with_stderr(
"\ "\
[COMPILING] foo v0.0.1 (CWD) [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)); let p = autodiscover_examples_project("2015", Some(false));
p.cargo("run --example a") p.cargo("run --example a")
.masquerade_as_nightly_cargo()
.with_status(101) .with_status(101)
.with_stderr("error: no example target named `a`\n") .with_stderr("error: no example target named `a`\n")
.run(); .run();
@ -459,7 +454,6 @@ fn run_example_autodiscover_2018() {
let p = autodiscover_examples_project("2018", None); let p = autodiscover_examples_project("2018", None);
p.cargo("run --example a") p.cargo("run --example a")
.masquerade_as_nightly_cargo()
.with_stderr( .with_stderr(
"\ "\
[COMPILING] foo v0.0.1 (CWD) [COMPILING] foo v0.0.1 (CWD)