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 all 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
16 changes: 14 additions & 2 deletions doc/src/overrides.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ profile = "minimal"
```

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 +104,19 @@ 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. To use a custom local
toolchain, you can instead use a `path` toolchain:

``` toml
[toolchain]
path = "/path/to/local/toolchain"
```

Since a `path` directive directly names a local toolchain, other options
like `components`, `targets`, and `profile` have no effect. `channel`
and `path` are mutually exclusive, since a `path` already points to a
specific toolchain. A relative `path` is resolved relative to the
location of the `rust-toolchain.toml` file.

## Default toolchain

Expand Down
94 changes: 78 additions & 16 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,38 @@ 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()
}
}

impl<T: Into<String>> From<T> for OverrideFile {
fn from(channel: T) -> Self {
Self {
toolchain: ToolchainSection {
channel: Some(channel.into()),
..Default::default()
},
let override_ = channel.into();
if Path::new(&override_).is_absolute() {
Self {
toolchain: ToolchainSection {
path: Some(PathBuf::from(override_)),
..Default::default()
},
}
} else {
Self {
toolchain: ToolchainSection {
channel: Some(override_),
..Default::default()
},
}
}
}
}
Expand All @@ -74,7 +88,7 @@ impl Display for OverrideReason {
}
}

#[derive(Default)]
#[derive(Default, Debug)]
struct OverrideCfg<'a> {
toolchain: Option<Toolchain<'a>>,
components: Vec<String>,
Expand All @@ -83,11 +97,27 @@ 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)) => {
if file.toolchain.targets.is_some()
|| file.toolchain.components.is_some()
|| file.toolchain.profile.is_some()
{
return Err(ErrorKind::CannotSpecifyPathAndOptions(path.into()).into());
}
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 @@ -522,15 +552,21 @@ impl Cfg {
}
OverrideReason::OverrideDB(ref path) => format!(
"the directory override for '{}' specifies an uninstalled toolchain",
path.display()
utils::canonicalize_path(path, self.notify_handler.as_ref()).display(),
),
OverrideReason::ToolchainFile(ref path) => format!(
"the toolchain file at '{}' specifies an uninstalled toolchain",
path.display()
utils::canonicalize_path(path, self.notify_handler.as_ref()).display(),
),
};

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 All @@ -557,8 +593,7 @@ impl Cfg {
settings: &Settings,
) -> Result<Option<(OverrideFile, OverrideReason)>> {
let notify = self.notify_handler.as_ref();
let dir = utils::canonicalize_path(dir, notify);
let mut dir = Some(&*dir);
let mut dir = Some(dir);

while let Some(d) = dir {
// First check the override database
Expand Down Expand Up @@ -955,6 +990,7 @@ mod tests {
OverrideFile {
toolchain: ToolchainSection {
channel: Some(contents.into()),
path: None,
components: None,
targets: None,
profile: None,
Expand All @@ -978,6 +1014,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 +1038,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 +1081,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 +1103,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 +1124,7 @@ components = [ "rustfmt" ]
OverrideFile {
toolchain: ToolchainSection {
channel: None,
path: None,
components: Some(vec!["rustfmt".into()]),
targets: None,
profile: None,
Expand Down
12 changes: 12 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,18 @@ error_chain! {
description("invalid toolchain name")
display("invalid toolchain name: '{}'", t)
}
InvalidToolchainPath(p: PathBuf) {
description("invalid toolchain path"),
display("invalid toolchain path: '{}'", p.to_string_lossy())
}
CannotSpecifyPathAndOptions(path: PathBuf) {
description("toolchain options are ignored for path toolchains"),
display("toolchain options are ignored for path toolchain ({})", path.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
10 changes: 8 additions & 2 deletions src/notifications.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,14 @@ impl<'a> Display for Notification<'a> {
} => write!(
f,
"both `{0}` and `{1}` exist. Using `{0}`",
rust_toolchain.display(),
rust_toolchain_toml.display()
rust_toolchain
.canonicalize()
.unwrap_or_else(|_| PathBuf::from(rust_toolchain))
.display(),
rust_toolchain_toml
.canonicalize()
.unwrap_or_else(|_| PathBuf::from(rust_toolchain_toml))
.display(),
),
}
}
Expand Down
37 changes: 37 additions & 0 deletions src/toolchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,34 @@ 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());
}

Ok(Toolchain {
cfg,
name: utils::canonicalize_path(&path, cfg.notify_handler.as_ref())
.to_str()
.ok_or_else(|| ErrorKind::InvalidToolchainPath(path.clone().into()))?
.to_owned(),
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 +284,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
Loading