Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add path override to rust-toolchain.toml #2678

Merged
merged 22 commits into from
Mar 17, 2021
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions doc/src/overrides.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,16 @@ a directory, the latter is used for backwards compatibility. The files use the
``` toml
[toolchain]
channel = "nightly-2020-07-10"
# or
path = "/path/to/local/toolchain"

components = [ "rustfmt", "rustc-dev" ]
targets = [ "wasm32-unknown-unknown", "thumbv2-none-eabi" ]
profile = "minimal"
jonhoo marked this conversation as resolved.
Show resolved Hide resolved
```

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

For backwards compatibility, `rust-toolchain` files also support a legacy
format that only contains a toolchain name without any TOML encoding, e.g.
Expand All @@ -104,7 +107,9 @@ The toolchains named in these files have a more restricted form than `rustup`
toolchains generally, and may only contain the names of the three release
channels, 'stable', 'beta', 'nightly', Rust version numbers, like '1.0.0', and
optionally an archive date, like 'nightly-2017-01-01'. They may not name
custom toolchains, nor host-specific toolchains.
custom toolchains, nor host-specific toolchains, except by giving the
`path` to said toolchain directly. A relative `path` is resolved
relative to the location of the `rust-toolchain.toml` file.

## Default toolchain

Expand Down
59 changes: 52 additions & 7 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,18 @@ impl OverrideFile {
#[derive(Debug, Default, Deserialize, PartialEq, Eq)]
struct ToolchainSection {
channel: Option<String>,
path: Option<PathBuf>,
components: Option<Vec<String>>,
targets: Option<Vec<String>>,
profile: Option<String>,
}

impl ToolchainSection {
fn is_empty(&self) -> bool {
self.channel.is_none() && self.components.is_none() && self.targets.is_none()
self.channel.is_none()
&& self.components.is_none()
&& self.targets.is_none()
&& self.path.is_none()
}
}

Expand Down Expand Up @@ -74,7 +78,7 @@ impl Display for OverrideReason {
}
}

#[derive(Default)]
#[derive(Default, Debug)]
struct OverrideCfg<'a> {
toolchain: Option<Toolchain<'a>>,
components: Vec<String>,
Expand All @@ -83,11 +87,19 @@ struct OverrideCfg<'a> {
}

impl<'a> OverrideCfg<'a> {
fn from_file(cfg: &'a Cfg, file: OverrideFile) -> Result<Self> {
fn from_file(
cfg: &'a Cfg,
cfg_path: Option<impl AsRef<Path>>,
file: OverrideFile,
) -> Result<Self> {
Ok(Self {
toolchain: match file.toolchain.channel {
Some(name) => Some(Toolchain::from(cfg, &name)?),
None => None,
toolchain: match (file.toolchain.channel, file.toolchain.path) {
(Some(name), None) => Some(Toolchain::from(cfg, &name)?),
(None, Some(path)) => Some(Toolchain::from_path(cfg, cfg_path, &path)?),
(Some(channel), Some(path)) => {
return Err(ErrorKind::CannotSpecifyChannelAndPath(channel, path.into()).into())
}
(None, None) => None,
},
components: file.toolchain.components.unwrap_or_default(),
targets: file.toolchain.targets.unwrap_or_default(),
Expand Down Expand Up @@ -530,7 +542,13 @@ impl Cfg {
),
};

let override_cfg = OverrideCfg::from_file(self, file)?;
let cfg_file = if let OverrideReason::ToolchainFile(ref path) = reason {
Some(path)
} else {
None
};

let override_cfg = OverrideCfg::from_file(self, cfg_file, file)?;
if let Some(toolchain) = &override_cfg.toolchain {
// Overridden toolchains can be literally any string, but only
// distributable toolchains will be auto-installed by the wrapping
Expand Down Expand Up @@ -955,6 +973,7 @@ mod tests {
OverrideFile {
toolchain: ToolchainSection {
channel: Some(contents.into()),
path: None,
components: None,
targets: None,
profile: None,
Expand All @@ -978,6 +997,7 @@ profile = "default"
OverrideFile {
toolchain: ToolchainSection {
channel: Some("nightly-2020-07-10".into()),
path: None,
components: Some(vec!["rustfmt".into(), "rustc-dev".into()]),
targets: Some(vec![
"wasm32-unknown-unknown".into(),
Expand All @@ -1001,6 +1021,28 @@ channel = "nightly-2020-07-10"
OverrideFile {
toolchain: ToolchainSection {
channel: Some("nightly-2020-07-10".into()),
path: None,
components: None,
targets: None,
profile: None,
}
}
);
}

#[test]
fn parse_toml_toolchain_file_only_path() {
let contents = r#"[toolchain]
path = "foobar"
"#;

let result = Cfg::parse_override_file(contents, ParseMode::Both);
assert_eq!(
result.unwrap(),
OverrideFile {
toolchain: ToolchainSection {
channel: None,
path: Some("foobar".into()),
components: None,
targets: None,
profile: None,
Expand All @@ -1022,6 +1064,7 @@ components = []
OverrideFile {
toolchain: ToolchainSection {
channel: Some("nightly-2020-07-10".into()),
path: None,
components: Some(vec![]),
targets: None,
profile: None,
Expand All @@ -1043,6 +1086,7 @@ targets = []
OverrideFile {
toolchain: ToolchainSection {
channel: Some("nightly-2020-07-10".into()),
path: None,
components: None,
targets: Some(vec![]),
profile: None,
Expand All @@ -1063,6 +1107,7 @@ components = [ "rustfmt" ]
OverrideFile {
toolchain: ToolchainSection {
channel: None,
path: None,
components: Some(vec!["rustfmt".into()]),
targets: None,
profile: None,
Expand Down
8 changes: 8 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,14 @@ error_chain! {
description("invalid toolchain name")
display("invalid toolchain name: '{}'", t)
}
InvalidToolchainPath(p: PathBuf) {
description("invalid toolchain path"),
display("invalid toolchain path: '{}'", p.display())
}
CannotSpecifyChannelAndPath(channel: String, path: PathBuf) {
description("cannot specify channel and path simultaneously"),
display("cannot specify both channel ({}) and path ({}) simultaneously", channel, path.display())
}
InvalidProfile(t: String) {
description("invalid profile name")
display("invalid profile name: '{}'; valid names are: {}", t, valid_profile_names())
Expand Down
40 changes: 40 additions & 0 deletions src/toolchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,37 @@ impl<'a> Toolchain<'a> {
})
}

pub fn from_path(
cfg: &'a Cfg,
cfg_file: Option<impl AsRef<Path>>,
path: impl AsRef<Path>,
) -> Result<Self> {
let path = if let Some(cfg_file) = cfg_file {
cfg_file.as_ref().parent().unwrap().join(path)
} else {
path.as_ref().to_path_buf()
};

// Perform minimal validation; there should at least be a `bin/` that might
// contain things for us to run.
if !path.join("bin").is_dir() {
return Err(ErrorKind::InvalidToolchainPath(path.into()).into());
}

let base = path
.components()
.last()
.ok_or_else(|| ErrorKind::InvalidToolchainPath(path.clone()))?
.as_os_str()
.to_string_lossy();
Ok(Toolchain {
cfg,
name: base.into(),
path,
dist_handler: Box::new(move |n| (cfg.notify_handler)(n.into())),
})
}

pub fn as_installed_common(&'a self) -> Result<InstalledCommonToolchain<'a>> {
if !self.exists() {
// Should be verify perhaps?
Expand Down Expand Up @@ -256,6 +287,15 @@ impl<'a> Toolchain<'a> {
}
}

impl<'a> std::fmt::Debug for Toolchain<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Toolchain")
.field("name", &self.name)
.field("path", &self.path)
.finish()
}
}

/// Newtype hosting functions that apply to both custom and distributable toolchains that are installed.
pub struct InstalledCommonToolchain<'a>(&'a Toolchain<'a>);

Expand Down
87 changes: 87 additions & 0 deletions tests/cli-rustup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1384,6 +1384,93 @@ fn file_override() {
});
}

#[test]
#[cfg_attr(
not(unix),
ignore = "TODO: Figure out how to write a wrapper toolchain on Windows"
jonhoo marked this conversation as resolved.
Show resolved Hide resolved
)]
fn file_override_path() {
setup(&|config| {
let cwd = config.current_dir();
let toolchain_path = cwd.join("ephemeral");
let toolchain_bin = toolchain_path.join("bin");
fs::create_dir_all(&toolchain_bin).unwrap();

// Inject a wrapper binary for rustc.
let rustc = toolchain_bin.join("rustc");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
raw::write_file(&rustc, "#!/bin/sh\necho custom-toolchain").unwrap();
fs::set_permissions(rustc, fs::Permissions::from_mode(0o755)).unwrap();
}

let toolchain_file = cwd.join("rust-toolchain.toml");
raw::write_file(
&toolchain_file,
&format!("[toolchain]\npath=\"{}\"", toolchain_path.display()),
)
.unwrap();

expect_stdout_ok(config, &["rustc", "--version"], "custom-toolchain");
});
}

#[test]
#[cfg_attr(
not(unix),
ignore = "TODO: Figure out how to write a wrapper toolchain on Windows"
)]
fn file_override_path_relative() {
setup(&|config| {
let cwd = config.current_dir();
let toolchain_path = cwd.join("ephemeral");
let toolchain_bin = toolchain_path.join("bin");
fs::create_dir_all(&toolchain_bin).unwrap();

// Inject a wrapper binary for rustc.
let rustc = toolchain_bin.join("rustc");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
raw::write_file(&rustc, "#!/bin/sh\necho custom-toolchain").unwrap();
fs::set_permissions(rustc, fs::Permissions::from_mode(0o755)).unwrap();
}

let toolchain_file = cwd.join("rust-toolchain.toml");
raw::write_file(&toolchain_file, "[toolchain]\npath=\"ephemeral\"").unwrap();

// Change into ephemeral so that we actually test that the path is relative to the override
config.change_dir(&toolchain_path, || {
expect_stdout_ok(config, &["rustc", "--version"], "custom-toolchain")
});
});
}

#[test]
fn file_override_path_xor_channel() {
jonhoo marked this conversation as resolved.
Show resolved Hide resolved
setup(&|config| {
// Make a plausible-looking toolchain
let cwd = config.current_dir();
let toolchain_path = cwd.join("ephemeral");
let toolchain_bin = toolchain_path.join("bin");
fs::create_dir_all(&toolchain_bin).unwrap();

let toolchain_file = cwd.join("rust-toolchain.toml");
raw::write_file(
&toolchain_file,
"[toolchain]\npath=\"ephemeral\"\nchannel=\"nightly\"",
)
.unwrap();

expect_err(
config,
&["rustc", "--version"],
"cannot specify both channel (nightly) and path (ephemeral) simultaneously",
);
});
}

#[test]
fn file_override_subdir() {
setup(&|config| {
Expand Down