diff --git a/src/config.rs b/src/config.rs index 2d26d7bdcdb..0aebd1ea6ac 100644 --- a/src/config.rs +++ b/src/config.rs @@ -33,6 +33,7 @@ impl OverrideFile { #[derive(Debug, Default, Deserialize, PartialEq, Eq)] struct ToolchainSection { channel: Option, + path: Option, components: Option>, targets: Option>, } @@ -45,11 +46,21 @@ impl ToolchainSection { impl> From for OverrideFile { fn from(channel: T) -> Self { - Self { - toolchain: ToolchainSection { - channel: Some(channel.into()), - ..Default::default() - }, + let channel = channel.into(); + if channel.contains('/') || channel.contains('\\') { + Self { + toolchain: ToolchainSection { + path: Some(channel), + ..Default::default() + }, + } + } else { + Self { + toolchain: ToolchainSection { + channel: Some(channel), + ..Default::default() + }, + } } } } @@ -73,7 +84,7 @@ impl Display for OverrideReason { } } -#[derive(Default)] +#[derive(Default, Debug)] struct OverrideCfg<'a> { toolchain: Option>, components: Vec, @@ -83,9 +94,13 @@ struct OverrideCfg<'a> { impl<'a> OverrideCfg<'a> { fn from_file(cfg: &'a Cfg, file: OverrideFile) -> Result { 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, &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(), @@ -514,7 +529,6 @@ impl Cfg { path.display() ), }; - let override_cfg = OverrideCfg::from_file(self, file)?; if let Some(toolchain) = &override_cfg.toolchain { // Overridden toolchains can be literally any string, but only @@ -882,6 +896,7 @@ mod tests { OverrideFile { toolchain: ToolchainSection { channel: Some(contents.into()), + path: None, components: None, targets: None, } @@ -903,6 +918,7 @@ targets = [ "wasm32-unknown-unknown", "thumbv2-none-eabi" ] 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(), @@ -925,6 +941,7 @@ channel = "nightly-2020-07-10" OverrideFile { toolchain: ToolchainSection { channel: Some("nightly-2020-07-10".into()), + path: None, components: None, targets: None, } @@ -945,6 +962,7 @@ components = [] OverrideFile { toolchain: ToolchainSection { channel: Some("nightly-2020-07-10".into()), + path: None, components: Some(vec![]), targets: None, } @@ -965,6 +983,7 @@ targets = [] OverrideFile { toolchain: ToolchainSection { channel: Some("nightly-2020-07-10".into()), + path: None, components: None, targets: Some(vec![]), } @@ -984,6 +1003,7 @@ components = [ "rustfmt" ] OverrideFile { toolchain: ToolchainSection { channel: None, + path: None, components: Some(vec!["rustfmt".into()]), targets: None, } diff --git a/src/errors.rs b/src/errors.rs index eb411de3716..ae177b9683d 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -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()) diff --git a/src/toolchain.rs b/src/toolchain.rs index d73d9709822..777eb071703 100644 --- a/src/toolchain.rs +++ b/src/toolchain.rs @@ -63,6 +63,9 @@ pub enum UpdateStatus { impl<'a> Toolchain<'a> { pub fn from(cfg: &'a Cfg, name: &str) -> Result { + if name.contains('/') || name.contains('\\') { + return Self::from_path(cfg, name); + } let resolved_name = cfg.resolve_toolchain(name)?; let path = cfg.toolchains_dir.join(&resolved_name); Ok(Toolchain { @@ -73,6 +76,26 @@ impl<'a> Toolchain<'a> { }) } + pub fn from_path(cfg: &'a Cfg, path: impl AsRef) -> Result { + let path = path.as_ref(); + let base = path + .components() + .last() + .ok_or_else(|| ErrorKind::InvalidToolchainPath(path.into()))? + .as_os_str() + .to_string_lossy(); + // Perform minimal validation - that there's a `bin/` which might contain things for us to run + if !path.join("bin").is_dir() { + return Err(ErrorKind::InvalidToolchainPath(path.into()).into()); + } + Ok(Toolchain { + cfg, + name: base.into(), + path: path.to_path_buf(), + dist_handler: Box::new(move |n| (cfg.notify_handler)(n.into())), + }) + } + pub fn as_installed_common(&'a self) -> Result> { if !self.exists() { // Should be verify perhaps? @@ -250,6 +273,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>);