Skip to content

Commit ca0b042

Browse files
authored
Merge pull request #2678 from jonhoo/local-toolchain
Add path override to rust-toolchain.toml
2 parents 22d43d0 + e72d2fa commit ca0b042

File tree

6 files changed

+410
-20
lines changed

6 files changed

+410
-20
lines changed

doc/src/overrides.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ profile = "minimal"
8989
```
9090

9191
The `[toolchain]` section is mandatory, and at least one property must be
92-
specified.
92+
specified. `channel` and `path` are mutually exclusive.
9393

9494
For backwards compatibility, `rust-toolchain` files also support a legacy
9595
format that only contains a toolchain name without any TOML encoding, e.g.
@@ -104,7 +104,19 @@ The toolchains named in these files have a more restricted form than `rustup`
104104
toolchains generally, and may only contain the names of the three release
105105
channels, 'stable', 'beta', 'nightly', Rust version numbers, like '1.0.0', and
106106
optionally an archive date, like 'nightly-2017-01-01'. They may not name
107-
custom toolchains, nor host-specific toolchains.
107+
custom toolchains, nor host-specific toolchains. To use a custom local
108+
toolchain, you can instead use a `path` toolchain:
109+
110+
``` toml
111+
[toolchain]
112+
path = "/path/to/local/toolchain"
113+
```
114+
115+
Since a `path` directive directly names a local toolchain, other options
116+
like `components`, `targets`, and `profile` have no effect. `channel`
117+
and `path` are mutually exclusive, since a `path` already points to a
118+
specific toolchain. A relative `path` is resolved relative to the
119+
location of the `rust-toolchain.toml` file.
108120

109121
## Default toolchain
110122

src/config.rs

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,24 +33,38 @@ impl OverrideFile {
3333
#[derive(Debug, Default, Deserialize, PartialEq, Eq)]
3434
struct ToolchainSection {
3535
channel: Option<String>,
36+
path: Option<PathBuf>,
3637
components: Option<Vec<String>>,
3738
targets: Option<Vec<String>>,
3839
profile: Option<String>,
3940
}
4041

4142
impl ToolchainSection {
4243
fn is_empty(&self) -> bool {
43-
self.channel.is_none() && self.components.is_none() && self.targets.is_none()
44+
self.channel.is_none()
45+
&& self.components.is_none()
46+
&& self.targets.is_none()
47+
&& self.path.is_none()
4448
}
4549
}
4650

4751
impl<T: Into<String>> From<T> for OverrideFile {
4852
fn from(channel: T) -> Self {
49-
Self {
50-
toolchain: ToolchainSection {
51-
channel: Some(channel.into()),
52-
..Default::default()
53-
},
53+
let override_ = channel.into();
54+
if Path::new(&override_).is_absolute() {
55+
Self {
56+
toolchain: ToolchainSection {
57+
path: Some(PathBuf::from(override_)),
58+
..Default::default()
59+
},
60+
}
61+
} else {
62+
Self {
63+
toolchain: ToolchainSection {
64+
channel: Some(override_),
65+
..Default::default()
66+
},
67+
}
5468
}
5569
}
5670
}
@@ -74,7 +88,7 @@ impl Display for OverrideReason {
7488
}
7589
}
7690

77-
#[derive(Default)]
91+
#[derive(Default, Debug)]
7892
struct OverrideCfg<'a> {
7993
toolchain: Option<Toolchain<'a>>,
8094
components: Vec<String>,
@@ -83,11 +97,27 @@ struct OverrideCfg<'a> {
8397
}
8498

8599
impl<'a> OverrideCfg<'a> {
86-
fn from_file(cfg: &'a Cfg, file: OverrideFile) -> Result<Self> {
100+
fn from_file(
101+
cfg: &'a Cfg,
102+
cfg_path: Option<impl AsRef<Path>>,
103+
file: OverrideFile,
104+
) -> Result<Self> {
87105
Ok(Self {
88-
toolchain: match file.toolchain.channel {
89-
Some(name) => Some(Toolchain::from(cfg, &name)?),
90-
None => None,
106+
toolchain: match (file.toolchain.channel, file.toolchain.path) {
107+
(Some(name), None) => Some(Toolchain::from(cfg, &name)?),
108+
(None, Some(path)) => {
109+
if file.toolchain.targets.is_some()
110+
|| file.toolchain.components.is_some()
111+
|| file.toolchain.profile.is_some()
112+
{
113+
return Err(ErrorKind::CannotSpecifyPathAndOptions(path.into()).into());
114+
}
115+
Some(Toolchain::from_path(cfg, cfg_path, &path)?)
116+
}
117+
(Some(channel), Some(path)) => {
118+
return Err(ErrorKind::CannotSpecifyChannelAndPath(channel, path.into()).into())
119+
}
120+
(None, None) => None,
91121
},
92122
components: file.toolchain.components.unwrap_or_default(),
93123
targets: file.toolchain.targets.unwrap_or_default(),
@@ -522,15 +552,21 @@ impl Cfg {
522552
}
523553
OverrideReason::OverrideDB(ref path) => format!(
524554
"the directory override for '{}' specifies an uninstalled toolchain",
525-
path.display()
555+
utils::canonicalize_path(path, self.notify_handler.as_ref()).display(),
526556
),
527557
OverrideReason::ToolchainFile(ref path) => format!(
528558
"the toolchain file at '{}' specifies an uninstalled toolchain",
529-
path.display()
559+
utils::canonicalize_path(path, self.notify_handler.as_ref()).display(),
530560
),
531561
};
532562

533-
let override_cfg = OverrideCfg::from_file(self, file)?;
563+
let cfg_file = if let OverrideReason::ToolchainFile(ref path) = reason {
564+
Some(path)
565+
} else {
566+
None
567+
};
568+
569+
let override_cfg = OverrideCfg::from_file(self, cfg_file, file)?;
534570
if let Some(toolchain) = &override_cfg.toolchain {
535571
// Overridden toolchains can be literally any string, but only
536572
// distributable toolchains will be auto-installed by the wrapping
@@ -557,8 +593,7 @@ impl Cfg {
557593
settings: &Settings,
558594
) -> Result<Option<(OverrideFile, OverrideReason)>> {
559595
let notify = self.notify_handler.as_ref();
560-
let dir = utils::canonicalize_path(dir, notify);
561-
let mut dir = Some(&*dir);
596+
let mut dir = Some(dir);
562597

563598
while let Some(d) = dir {
564599
// First check the override database
@@ -955,6 +990,7 @@ mod tests {
955990
OverrideFile {
956991
toolchain: ToolchainSection {
957992
channel: Some(contents.into()),
993+
path: None,
958994
components: None,
959995
targets: None,
960996
profile: None,
@@ -978,6 +1014,7 @@ profile = "default"
9781014
OverrideFile {
9791015
toolchain: ToolchainSection {
9801016
channel: Some("nightly-2020-07-10".into()),
1017+
path: None,
9811018
components: Some(vec!["rustfmt".into(), "rustc-dev".into()]),
9821019
targets: Some(vec![
9831020
"wasm32-unknown-unknown".into(),
@@ -1001,6 +1038,28 @@ channel = "nightly-2020-07-10"
10011038
OverrideFile {
10021039
toolchain: ToolchainSection {
10031040
channel: Some("nightly-2020-07-10".into()),
1041+
path: None,
1042+
components: None,
1043+
targets: None,
1044+
profile: None,
1045+
}
1046+
}
1047+
);
1048+
}
1049+
1050+
#[test]
1051+
fn parse_toml_toolchain_file_only_path() {
1052+
let contents = r#"[toolchain]
1053+
path = "foobar"
1054+
"#;
1055+
1056+
let result = Cfg::parse_override_file(contents, ParseMode::Both);
1057+
assert_eq!(
1058+
result.unwrap(),
1059+
OverrideFile {
1060+
toolchain: ToolchainSection {
1061+
channel: None,
1062+
path: Some("foobar".into()),
10041063
components: None,
10051064
targets: None,
10061065
profile: None,
@@ -1022,6 +1081,7 @@ components = []
10221081
OverrideFile {
10231082
toolchain: ToolchainSection {
10241083
channel: Some("nightly-2020-07-10".into()),
1084+
path: None,
10251085
components: Some(vec![]),
10261086
targets: None,
10271087
profile: None,
@@ -1043,6 +1103,7 @@ targets = []
10431103
OverrideFile {
10441104
toolchain: ToolchainSection {
10451105
channel: Some("nightly-2020-07-10".into()),
1106+
path: None,
10461107
components: None,
10471108
targets: Some(vec![]),
10481109
profile: None,
@@ -1063,6 +1124,7 @@ components = [ "rustfmt" ]
10631124
OverrideFile {
10641125
toolchain: ToolchainSection {
10651126
channel: None,
1127+
path: None,
10661128
components: Some(vec!["rustfmt".into()]),
10671129
targets: None,
10681130
profile: None,

src/errors.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,18 @@ error_chain! {
188188
description("invalid toolchain name")
189189
display("invalid toolchain name: '{}'", t)
190190
}
191+
InvalidToolchainPath(p: PathBuf) {
192+
description("invalid toolchain path"),
193+
display("invalid toolchain path: '{}'", p.to_string_lossy())
194+
}
195+
CannotSpecifyPathAndOptions(path: PathBuf) {
196+
description("toolchain options are ignored for path toolchains"),
197+
display("toolchain options are ignored for path toolchain ({})", path.display())
198+
}
199+
CannotSpecifyChannelAndPath(channel: String, path: PathBuf) {
200+
description("cannot specify channel and path simultaneously"),
201+
display("cannot specify both channel ({}) and path ({}) simultaneously", channel, path.display())
202+
}
191203
InvalidProfile(t: String) {
192204
description("invalid profile name")
193205
display("invalid profile name: '{}'; valid names are: {}", t, valid_profile_names())

src/notifications.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,14 @@ impl<'a> Display for Notification<'a> {
143143
} => write!(
144144
f,
145145
"both `{0}` and `{1}` exist. Using `{0}`",
146-
rust_toolchain.display(),
147-
rust_toolchain_toml.display()
146+
rust_toolchain
147+
.canonicalize()
148+
.unwrap_or_else(|_| PathBuf::from(rust_toolchain))
149+
.display(),
150+
rust_toolchain_toml
151+
.canonicalize()
152+
.unwrap_or_else(|_| PathBuf::from(rust_toolchain_toml))
153+
.display(),
148154
),
149155
}
150156
}

src/toolchain.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,34 @@ impl<'a> Toolchain<'a> {
7474
})
7575
}
7676

77+
pub fn from_path(
78+
cfg: &'a Cfg,
79+
cfg_file: Option<impl AsRef<Path>>,
80+
path: impl AsRef<Path>,
81+
) -> Result<Self> {
82+
let path = if let Some(cfg_file) = cfg_file {
83+
cfg_file.as_ref().parent().unwrap().join(path)
84+
} else {
85+
path.as_ref().to_path_buf()
86+
};
87+
88+
// Perform minimal validation; there should at least be a `bin/` that might
89+
// contain things for us to run.
90+
if !path.join("bin").is_dir() {
91+
return Err(ErrorKind::InvalidToolchainPath(path.into()).into());
92+
}
93+
94+
Ok(Toolchain {
95+
cfg,
96+
name: utils::canonicalize_path(&path, cfg.notify_handler.as_ref())
97+
.to_str()
98+
.ok_or_else(|| ErrorKind::InvalidToolchainPath(path.clone().into()))?
99+
.to_owned(),
100+
path,
101+
dist_handler: Box::new(move |n| (cfg.notify_handler)(n.into())),
102+
})
103+
}
104+
77105
pub fn as_installed_common(&'a self) -> Result<InstalledCommonToolchain<'a>> {
78106
if !self.exists() {
79107
// Should be verify perhaps?
@@ -256,6 +284,15 @@ impl<'a> Toolchain<'a> {
256284
}
257285
}
258286

287+
impl<'a> std::fmt::Debug for Toolchain<'a> {
288+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
289+
f.debug_struct("Toolchain")
290+
.field("name", &self.name)
291+
.field("path", &self.path)
292+
.finish()
293+
}
294+
}
295+
259296
/// Newtype hosting functions that apply to both custom and distributable toolchains that are installed.
260297
pub struct InstalledCommonToolchain<'a>(&'a Toolchain<'a>);
261298

0 commit comments

Comments
 (0)