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.",
).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()),
)
}

View File

@ -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;
@ -131,8 +131,7 @@ impl<'cfg> Compilation<'cfg> {
/// See `process`.
pub fn rustc_process(&self, pkg: &Package, target: &Target) -> CargoResult<ProcessBuilder> {
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)
@ -141,8 +140,7 @@ impl<'cfg> Compilation<'cfg> {
/// See `process`.
pub fn rustdoc_process(&self, pkg: &Package, target: &Target) -> CargoResult<ProcessBuilder> {
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()));
}

View File

@ -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,

View File

@ -30,6 +30,7 @@ pub struct NewOptions {
/// Absolute path to the directory for the new project
pub path: PathBuf,
pub name: Option<String>,
pub edition: Option<String>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -65,6 +66,7 @@ struct MkOptions<'a> {
name: &'a str,
source_files: Vec<SourceFileInformation>,
bin: bool,
edition: Option<&'a str>,
}
impl NewOptions {
@ -74,6 +76,7 @@ impl NewOptions {
lib: bool,
path: PathBuf,
name: Option<String>,
edition: Option<String>,
) -> CargoResult<NewOptions> {
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(),
)?;

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.
* 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`,

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
* 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)

View File

@ -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"
[project]
name = "foo"
version = "0.0.1"
authors = []
edition = "2015"
[[bench]]
name = "bench_magic"
required-features = ["magic"]
"#,
[[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

View File

@ -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"
[package]
name = "foo"
version = "0.0.1"
authors = []
edition = "2018"
[lib]
edition = "2015"
"#,
).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_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();
p.cargo("build -v").run();
}
#[test]

View File

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

View File

@ -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();

View File

@ -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();
}

View File

@ -971,45 +971,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]
@ -1018,18 +992,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(
"\
@ -1044,39 +1016,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()

View File

@ -359,22 +359,20 @@ fn autodiscover_examples_project(rust_edition: &str, autoexamples: Option<bool>)
"Cargo.toml",
&format!(
r#"
cargo-features = ["edition"]
[project]
name = "foo"
version = "0.0.1"
authors = []
edition = "{rust_edition}"
{autoexamples}
[project]
name = "foo"
version = "0.0.1"
authors = []
edition = "{rust_edition}"
{autoexamples}
[features]
magic = []
[features]
magic = []
[[example]]
name = "do_magic"
required-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<bool>)
.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)