Skip to content

Commit 6eccd9b

Browse files
authored
Merge pull request #2653 from phil-opp/rust_toolchain_toml
Allow `.toml` extension for `rust-toolchain` files
2 parents 3c8f954 + 90beb56 commit 6eccd9b

File tree

4 files changed

+154
-31
lines changed

4 files changed

+154
-31
lines changed

doc/src/overrides.md

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ and override which toolchain is used:
88
+beta`.
99
2. The `RUSTUP_TOOLCHAIN` environment variable.
1010
3. A [directory override], set with the `rustup override` command.
11-
4. The [`rust-toolchain`] file.
11+
4. The [`rust-toolchain.toml`] file.
1212
5. The [default toolchain].
1313

1414
The toolchain is chosen in the order listed above, using the first one that is
1515
specified. There is one exception though: directory overrides and the
16-
`rust-toolchain` file are also preferred by their proximity to the current
16+
`rust-toolchain.toml` file are also preferred by their proximity to the current
1717
directory. That is, these two override methods are discovered by walking up
18-
the directory tree toward the filesystem root, and a `rust-toolchain` file
18+
the directory tree toward the filesystem root, and a `rust-toolchain.toml` file
1919
that is closer to the current directory will be preferred over a directory
2020
override that is further away.
2121

@@ -24,7 +24,7 @@ To verify which toolchain is active use `rustup show`.
2424
[toolchain]: concepts/toolchains.md
2525
[toolchain override shorthand]: #toolchain-override-shorthand
2626
[directory override]: #directory-overrides
27-
[`rust-toolchain`]: #the-toolchain-file
27+
[`rust-toolchain.toml`]: #the-toolchain-file
2828
[default toolchain]: #default-toolchain
2929

3030
## Toolchain override shorthand
@@ -74,8 +74,11 @@ case for nightly-only software that pins to a revision from the release
7474
archives.
7575

7676
In these cases the toolchain can be named in the project's directory in a file
77-
called `rust-toolchain`, the content of which is either the name of a single
78-
`rustup` toolchain, or a TOML file with the following layout:
77+
called `rust-toolchain.toml` or `rust-toolchain`. If both files are present in
78+
a directory, the latter is used for backwards compatibility. The files use the
79+
[TOML] format and have the following layout:
80+
81+
[TOML]: https://toml.io/
7982

8083
``` toml
8184
[toolchain]
@@ -85,14 +88,19 @@ targets = [ "wasm32-unknown-unknown", "thumbv2-none-eabi" ]
8588
profile = "minimal"
8689
```
8790

88-
If the TOML format is used, the `[toolchain]` section is mandatory, and at
89-
least one property must be specified.
91+
The `[toolchain]` section is mandatory, and at least one property must be
92+
specified.
93+
94+
For backwards compatibility, `rust-toolchain` files also support a legacy
95+
format that only contains a toolchain name without any TOML encoding, e.g.
96+
just `nightly-2021-01-21`. The file has to be encoded in US-ASCII this case
97+
(if you are on Windows, check the encoding and that it does not starts with a
98+
BOM). The legacy format is not available in `rust-toolchain.toml` files.
9099

91-
The `rust-toolchain` file is suitable to check in to source control. This file
92-
has to be encoded in US-ASCII (if you are on Windows, check the encoding and
93-
that it does not starts with a BOM).
100+
The `rust-toolchain.toml`/`rust-toolchain` files are suitable to check in to
101+
source control.
94102

95-
The toolchains named in this file have a more restricted form than `rustup`
103+
The toolchains named in these files have a more restricted form than `rustup`
96104
toolchains generally, and may only contain the names of the three release
97105
channels, 'stable', 'beta', 'nightly', Rust version numbers, like '1.0.0', and
98106
optionally an archive date, like 'nightly-2017-01-01'. They may not name

src/config.rs

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -567,10 +567,36 @@ impl Cfg {
567567
return Ok(Some((name.into(), reason)));
568568
}
569569

570-
// Then look for 'rust-toolchain'
571-
let toolchain_file = d.join("rust-toolchain");
572-
if let Ok(contents) = utils::read_file("toolchain file", &toolchain_file) {
573-
let override_file = Cfg::parse_override_file(contents)?;
570+
// Then look for 'rust-toolchain' or 'rust-toolchain.toml'
571+
let path_rust_toolchain = d.join("rust-toolchain");
572+
let path_rust_toolchain_toml = d.join("rust-toolchain.toml");
573+
574+
let (toolchain_file, contents, parse_mode) = match (
575+
utils::read_file("toolchain file", &path_rust_toolchain),
576+
utils::read_file("toolchain file", &path_rust_toolchain_toml),
577+
) {
578+
(contents, Err(_)) => {
579+
// no `rust-toolchain.toml` exists
580+
(path_rust_toolchain, contents, ParseMode::Both)
581+
}
582+
(Err(_), Ok(contents)) => {
583+
// only `rust-toolchain.toml` exists
584+
(path_rust_toolchain_toml, Ok(contents), ParseMode::OnlyToml)
585+
}
586+
(Ok(contents), Ok(_)) => {
587+
// both `rust-toolchain` and `rust-toolchain.toml` exist
588+
589+
notify(Notification::DuplicateToolchainFile {
590+
rust_toolchain: &path_rust_toolchain,
591+
rust_toolchain_toml: &path_rust_toolchain_toml,
592+
});
593+
594+
(path_rust_toolchain, Ok(contents), ParseMode::Both)
595+
}
596+
};
597+
598+
if let Ok(contents) = contents {
599+
let override_file = Cfg::parse_override_file(contents, parse_mode)?;
574600
if let Some(toolchain_name) = &override_file.toolchain.channel {
575601
let all_toolchains = self.list_toolchains()?;
576602
if !all_toolchains.iter().any(|s| s == toolchain_name) {
@@ -590,12 +616,15 @@ impl Cfg {
590616
Ok(None)
591617
}
592618

593-
fn parse_override_file<S: AsRef<str>>(contents: S) -> Result<OverrideFile> {
619+
fn parse_override_file<S: AsRef<str>>(
620+
contents: S,
621+
parse_mode: ParseMode,
622+
) -> Result<OverrideFile> {
594623
let contents = contents.as_ref();
595624

596-
match contents.lines().count() {
597-
0 => Err(ErrorKind::EmptyOverrideFile.into()),
598-
1 => {
625+
match (contents.lines().count(), parse_mode) {
626+
(0, _) => Err(ErrorKind::EmptyOverrideFile.into()),
627+
(1, ParseMode::Both) => {
599628
let channel = contents.trim();
600629

601630
if channel.is_empty() {
@@ -898,6 +927,20 @@ impl Cfg {
898927
}
899928
}
900929

930+
/// Specifies how a `rust-toolchain`/`rust-toolchain.toml` configuration file should be parsed.
931+
enum ParseMode {
932+
/// Only permit TOML format in a configuration file.
933+
///
934+
/// This variant is used for `rust-toolchain.toml` files (with `.toml` extension).
935+
OnlyToml,
936+
/// Permit both the legacy format (i.e. just the channel name) and the TOML format in
937+
/// a configuration file.
938+
///
939+
/// This variant is used for `rust-toolchain` files (no file extension) for backwards
940+
/// compatibility.
941+
Both,
942+
}
943+
901944
#[cfg(test)]
902945
mod tests {
903946
use super::*;
@@ -906,7 +949,7 @@ mod tests {
906949
fn parse_legacy_toolchain_file() {
907950
let contents = "nightly-2020-07-10";
908951

909-
let result = Cfg::parse_override_file(contents);
952+
let result = Cfg::parse_override_file(contents, ParseMode::Both);
910953
assert_eq!(
911954
result.unwrap(),
912955
OverrideFile {
@@ -929,7 +972,7 @@ targets = [ "wasm32-unknown-unknown", "thumbv2-none-eabi" ]
929972
profile = "default"
930973
"#;
931974

932-
let result = Cfg::parse_override_file(contents);
975+
let result = Cfg::parse_override_file(contents, ParseMode::Both);
933976
assert_eq!(
934977
result.unwrap(),
935978
OverrideFile {
@@ -952,7 +995,7 @@ profile = "default"
952995
channel = "nightly-2020-07-10"
953996
"#;
954997

955-
let result = Cfg::parse_override_file(contents);
998+
let result = Cfg::parse_override_file(contents, ParseMode::Both);
956999
assert_eq!(
9571000
result.unwrap(),
9581001
OverrideFile {
@@ -973,7 +1016,7 @@ channel = "nightly-2020-07-10"
9731016
components = []
9741017
"#;
9751018

976-
let result = Cfg::parse_override_file(contents);
1019+
let result = Cfg::parse_override_file(contents, ParseMode::Both);
9771020
assert_eq!(
9781021
result.unwrap(),
9791022
OverrideFile {
@@ -994,7 +1037,7 @@ channel = "nightly-2020-07-10"
9941037
targets = []
9951038
"#;
9961039

997-
let result = Cfg::parse_override_file(contents);
1040+
let result = Cfg::parse_override_file(contents, ParseMode::Both);
9981041
assert_eq!(
9991042
result.unwrap(),
10001043
OverrideFile {
@@ -1014,7 +1057,7 @@ targets = []
10141057
components = [ "rustfmt" ]
10151058
"#;
10161059

1017-
let result = Cfg::parse_override_file(contents);
1060+
let result = Cfg::parse_override_file(contents, ParseMode::Both);
10181061
assert_eq!(
10191062
result.unwrap(),
10201063
OverrideFile {
@@ -1034,7 +1077,7 @@ components = [ "rustfmt" ]
10341077
[toolchain]
10351078
"#;
10361079

1037-
let result = Cfg::parse_override_file(contents);
1080+
let result = Cfg::parse_override_file(contents, ParseMode::Both);
10381081
assert!(matches!(
10391082
result.unwrap_err().kind(),
10401083
ErrorKind::InvalidOverrideFile
@@ -1045,7 +1088,7 @@ components = [ "rustfmt" ]
10451088
fn parse_empty_toolchain_file() {
10461089
let contents = "";
10471090

1048-
let result = Cfg::parse_override_file(contents);
1091+
let result = Cfg::parse_override_file(contents, ParseMode::Both);
10491092
assert!(matches!(
10501093
result.unwrap_err().kind(),
10511094
ErrorKind::EmptyOverrideFile
@@ -1056,7 +1099,7 @@ components = [ "rustfmt" ]
10561099
fn parse_whitespace_toolchain_file() {
10571100
let contents = " ";
10581101

1059-
let result = Cfg::parse_override_file(contents);
1102+
let result = Cfg::parse_override_file(contents, ParseMode::Both);
10601103
assert!(matches!(
10611104
result.unwrap_err().kind(),
10621105
ErrorKind::EmptyOverrideFile
@@ -1069,7 +1112,7 @@ components = [ "rustfmt" ]
10691112
channel = nightly
10701113
"#;
10711114

1072-
let result = Cfg::parse_override_file(contents);
1115+
let result = Cfg::parse_override_file(contents, ParseMode::Both);
10731116
assert!(matches!(
10741117
result.unwrap_err().kind(),
10751118
ErrorKind::ParsingOverrideFile(..)

src/notifications.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ pub enum Notification<'a> {
3333
UpgradeRemovesToolchains,
3434
MissingFileDuringSelfUninstall(PathBuf),
3535
PlainVerboseMessage(&'a str),
36+
/// Both `rust-toolchain` and `rust-toolchain.toml` exist within a directory
37+
DuplicateToolchainFile {
38+
rust_toolchain: &'a Path,
39+
rust_toolchain_toml: &'a Path,
40+
},
3641
}
3742

3843
impl<'a> From<crate::dist::Notification<'a>> for Notification<'a> {
@@ -77,7 +82,9 @@ impl<'a> Notification<'a> {
7782
| UpgradingMetadata(_, _)
7883
| MetadataUpgradeNotNeeded(_) => NotificationLevel::Info,
7984
NonFatalError(_) => NotificationLevel::Error,
80-
UpgradeRemovesToolchains | MissingFileDuringSelfUninstall(_) => NotificationLevel::Warn,
85+
UpgradeRemovesToolchains
86+
| MissingFileDuringSelfUninstall(_)
87+
| DuplicateToolchainFile { .. } => NotificationLevel::Warn,
8188
}
8289
}
8390
}
@@ -130,6 +137,15 @@ impl<'a> Display for Notification<'a> {
130137
p.display()
131138
),
132139
PlainVerboseMessage(r) => write!(f, "{}", r),
140+
DuplicateToolchainFile {
141+
rust_toolchain,
142+
rust_toolchain_toml,
143+
} => write!(
144+
f,
145+
"both `{0}` and `{1}` exist. Using `{0}`",
146+
rust_toolchain.display(),
147+
rust_toolchain_toml.display()
148+
),
133149
}
134150
}
135151
}

tests/cli-rustup.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2065,3 +2065,59 @@ warning: If you meant to build software to target that platform, perhaps try `ru
20652065
);
20662066
});
20672067
}
2068+
2069+
/// Checks that `rust-toolchain.toml` files are considered
2070+
#[test]
2071+
fn rust_toolchain_toml() {
2072+
setup(&|config| {
2073+
expect_err(
2074+
config,
2075+
&["rustc", "--version"],
2076+
"no override and no default toolchain set",
2077+
);
2078+
2079+
let cwd = config.current_dir();
2080+
let toolchain_file = cwd.join("rust-toolchain.toml");
2081+
raw::write_file(&toolchain_file, "[toolchain]\nchannel = \"nightly\"").unwrap();
2082+
2083+
expect_stdout_ok(config, &["rustc", "--version"], "hash-nightly-2");
2084+
});
2085+
}
2086+
2087+
/// Ensures that `rust-toolchain.toml` files (with `.toml` extension) only allow TOML contents
2088+
#[test]
2089+
fn only_toml_in_rust_toolchain_toml() {
2090+
setup(&|config| {
2091+
let cwd = config.current_dir();
2092+
let toolchain_file = cwd.join("rust-toolchain.toml");
2093+
raw::write_file(&toolchain_file, "nightly").unwrap();
2094+
2095+
expect_err(
2096+
config,
2097+
&["rustc", "--version"],
2098+
"error parsing override file",
2099+
);
2100+
});
2101+
}
2102+
2103+
/// Checks that a warning occurs if both `rust-toolchain` and `rust-toolchain.toml` files exist
2104+
#[test]
2105+
fn warn_on_duplicate_rust_toolchain_file() {
2106+
setup(&|config| {
2107+
let cwd = config.current_dir();
2108+
let toolchain_file_1 = cwd.join("rust-toolchain");
2109+
raw::write_file(&toolchain_file_1, "stable").unwrap();
2110+
let toolchain_file_2 = cwd.join("rust-toolchain.toml");
2111+
raw::write_file(&toolchain_file_2, "[toolchain]").unwrap();
2112+
2113+
expect_stderr_ok(
2114+
config,
2115+
&["rustc", "--version"],
2116+
&format!(
2117+
"warning: both `{0}` and `{1}` exist. Using `{0}`",
2118+
toolchain_file_1.canonicalize().unwrap().display(),
2119+
toolchain_file_2.canonicalize().unwrap().display(),
2120+
),
2121+
);
2122+
});
2123+
}

0 commit comments

Comments
 (0)