Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
| use crate::config::Cfg; | |
| use crate::env_var; | |
| use crate::errors::*; | |
| use crate::install::{self, InstallMethod}; | |
| use crate::notifications::*; | |
| use crate::telemetry; | |
| use crate::telemetry::{Telemetry, TelemetryEvent}; | |
| use rustup_dist; | |
| use rustup_dist::dist::ToolchainDesc; | |
| use rustup_dist::download::DownloadCfg; | |
| use rustup_dist::manifest::Component; | |
| use rustup_dist::manifestation::{Changes, Manifestation}; | |
| use rustup_dist::prefix::InstallPrefix; | |
| use rustup_utils::utils; | |
| use std::env; | |
| use std::env::consts::EXE_SUFFIX; | |
| use std::ffi::OsStr; | |
| use std::ffi::OsString; | |
| use std::path::{Path, PathBuf}; | |
| use std::process::Command; | |
| use url::Url; | |
| /// A fully resolved reference to a toolchain which may or may not exist | |
| pub struct Toolchain<'a> { | |
| cfg: &'a Cfg, | |
| name: String, | |
| path: PathBuf, | |
| telemetry: telemetry::Telemetry, | |
| dist_handler: Box<Fn(rustup_dist::Notification) + 'a>, | |
| } | |
| /// Used by the `list_component` function | |
| pub struct ComponentStatus { | |
| pub component: Component, | |
| pub name: String, | |
| pub required: bool, | |
| pub installed: bool, | |
| pub available: bool, | |
| } | |
| pub enum UpdateStatus { | |
| Installed, | |
| Updated, | |
| Unchanged, | |
| } | |
| impl<'a> Toolchain<'a> { | |
| pub fn from(cfg: &'a Cfg, name: &str) -> Result<Self> { | |
| let resolved_name = cfg.resolve_toolchain(name)?; | |
| let path = cfg.toolchains_dir.join(&resolved_name); | |
| Ok(Toolchain { | |
| cfg: cfg, | |
| name: resolved_name, | |
| path: path.clone(), | |
| telemetry: Telemetry::new(cfg.rustup_dir.join("telemetry")), | |
| dist_handler: Box::new(move |n| (cfg.notify_handler)(n.into())), | |
| }) | |
| } | |
| pub fn name(&self) -> &str { | |
| &self.name | |
| } | |
| pub fn desc(&self) -> Result<ToolchainDesc> { | |
| Ok(ToolchainDesc::from_str(&self.name)?) | |
| } | |
| pub fn path(&self) -> &Path { | |
| &self.path | |
| } | |
| fn is_symlink(&self) -> bool { | |
| use std::fs; | |
| fs::symlink_metadata(&self.path) | |
| .map(|m| m.file_type().is_symlink()) | |
| .unwrap_or(false) | |
| } | |
| pub fn exists(&self) -> bool { | |
| // HACK: linked toolchains are symlinks, and, contrary to what std docs | |
| // lead me to believe `fs::metadata`, used by `is_directory` does not | |
| // seem to follow symlinks on windows. | |
| let is_symlink = if cfg!(windows) { | |
| self.is_symlink() | |
| } else { | |
| false | |
| }; | |
| utils::is_directory(&self.path) || is_symlink | |
| } | |
| pub fn verify(&self) -> Result<()> { | |
| Ok(utils::assert_is_directory(&self.path)?) | |
| } | |
| pub fn remove(&self) -> Result<()> { | |
| if self.exists() || self.is_symlink() { | |
| (self.cfg.notify_handler)(Notification::UninstallingToolchain(&self.name)); | |
| } else { | |
| (self.cfg.notify_handler)(Notification::ToolchainNotInstalled(&self.name)); | |
| return Ok(()); | |
| } | |
| if let Some(update_hash) = self.update_hash()? { | |
| utils::ensure_file_removed("update hash", &update_hash)?; | |
| } | |
| let result = install::uninstall(&self.path, &|n| (self.cfg.notify_handler)(n.into())); | |
| if !self.exists() { | |
| (self.cfg.notify_handler)(Notification::UninstalledToolchain(&self.name)); | |
| } | |
| Ok(result?) | |
| } | |
| fn install(&self, install_method: InstallMethod) -> Result<UpdateStatus> { | |
| assert!(self.is_valid_install_method(install_method)); | |
| let exists = self.exists(); | |
| if exists { | |
| (self.cfg.notify_handler)(Notification::UpdatingToolchain(&self.name)); | |
| } else { | |
| (self.cfg.notify_handler)(Notification::InstallingToolchain(&self.name)); | |
| } | |
| (self.cfg.notify_handler)(Notification::ToolchainDirectory(&self.path, &self.name)); | |
| let updated = install_method.run(&self.path, &|n| (self.cfg.notify_handler)(n.into()))?; | |
| if !updated { | |
| (self.cfg.notify_handler)(Notification::UpdateHashMatches); | |
| } else { | |
| (self.cfg.notify_handler)(Notification::InstalledToolchain(&self.name)); | |
| } | |
| let status = match (updated, exists) { | |
| (true, false) => UpdateStatus::Installed, | |
| (true, true) => UpdateStatus::Updated, | |
| (false, true) => UpdateStatus::Unchanged, | |
| (false, false) => UpdateStatus::Unchanged, | |
| }; | |
| Ok(status) | |
| } | |
| fn install_if_not_installed(&self, install_method: InstallMethod) -> Result<UpdateStatus> { | |
| assert!(self.is_valid_install_method(install_method)); | |
| (self.cfg.notify_handler)(Notification::LookingForToolchain(&self.name)); | |
| if !self.exists() { | |
| Ok(self.install(install_method)?) | |
| } else { | |
| (self.cfg.notify_handler)(Notification::UsingExistingToolchain(&self.name)); | |
| Ok(UpdateStatus::Unchanged) | |
| } | |
| } | |
| fn is_valid_install_method(&self, install_method: InstallMethod) -> bool { | |
| match install_method { | |
| InstallMethod::Copy(_) | InstallMethod::Link(_) | InstallMethod::Installer(..) => { | |
| self.is_custom() | |
| } | |
| InstallMethod::Dist(..) => !self.is_custom(), | |
| } | |
| } | |
| fn update_hash(&self) -> Result<Option<PathBuf>> { | |
| if self.is_custom() { | |
| Ok(None) | |
| } else { | |
| Ok(Some(self.cfg.get_hash_file(&self.name, true)?)) | |
| } | |
| } | |
| fn download_cfg(&self) -> DownloadCfg { | |
| DownloadCfg { | |
| dist_root: &self.cfg.dist_root_url, | |
| temp_cfg: &self.cfg.temp_cfg, | |
| download_dir: &self.cfg.download_dir, | |
| notify_handler: &*self.dist_handler, | |
| } | |
| } | |
| pub fn install_from_dist(&self, force_update: bool) -> Result<UpdateStatus> { | |
| if self.cfg.telemetry_enabled()? { | |
| return self.install_from_dist_with_telemetry(force_update); | |
| } | |
| self.install_from_dist_inner(force_update) | |
| } | |
| pub fn install_from_dist_inner(&self, force_update: bool) -> Result<UpdateStatus> { | |
| let update_hash = self.update_hash()?; | |
| self.install(InstallMethod::Dist( | |
| &self.desc()?, | |
| update_hash.as_ref().map(|p| &**p), | |
| self.download_cfg(), | |
| force_update, | |
| )) | |
| } | |
| pub fn install_from_dist_with_telemetry(&self, force_update: bool) -> Result<UpdateStatus> { | |
| let result = self.install_from_dist_inner(force_update); | |
| match result { | |
| Ok(us) => { | |
| let te = TelemetryEvent::ToolchainUpdate { | |
| toolchain: self.name().to_string(), | |
| success: true, | |
| }; | |
| match self.telemetry.log_telemetry(te) { | |
| Ok(_) => Ok(us), | |
| Err(e) => { | |
| (self.cfg.notify_handler)(Notification::TelemetryCleanupError(&e)); | |
| Ok(us) | |
| } | |
| } | |
| } | |
| Err(e) => { | |
| let te = TelemetryEvent::ToolchainUpdate { | |
| toolchain: self.name().to_string(), | |
| success: true, | |
| }; | |
| let _ = self.telemetry.log_telemetry(te).map_err(|xe| { | |
| (self.cfg.notify_handler)(Notification::TelemetryCleanupError(&xe)); | |
| }); | |
| Err(e) | |
| } | |
| } | |
| } | |
| pub fn install_from_dist_if_not_installed(&self) -> Result<UpdateStatus> { | |
| let update_hash = self.update_hash()?; | |
| self.install_if_not_installed(InstallMethod::Dist( | |
| &self.desc()?, | |
| update_hash.as_ref().map(|p| &**p), | |
| self.download_cfg(), | |
| false, | |
| )) | |
| } | |
| pub fn is_custom(&self) -> bool { | |
| ToolchainDesc::from_str(&self.name).is_err() | |
| } | |
| pub fn is_tracking(&self) -> bool { | |
| ToolchainDesc::from_str(&self.name) | |
| .ok() | |
| .map(|d| d.is_tracking()) | |
| == Some(true) | |
| } | |
| fn ensure_custom(&self) -> Result<()> { | |
| if !self.is_custom() { | |
| Err( | |
| ErrorKind::Dist(::rustup_dist::ErrorKind::InvalidCustomToolchainName( | |
| self.name.to_string(), | |
| )) | |
| .into(), | |
| ) | |
| } else { | |
| Ok(()) | |
| } | |
| } | |
| pub fn install_from_installers(&self, installers: &[&OsStr]) -> Result<()> { | |
| self.ensure_custom()?; | |
| self.remove()?; | |
| // FIXME: This should do all downloads first, then do | |
| // installs, and do it all in a single transaction. | |
| for installer in installers { | |
| let installer_str = installer.to_str().unwrap_or("bogus"); | |
| match installer_str.rfind('.') { | |
| Some(i) => { | |
| let extension = &installer_str[i + 1..]; | |
| if extension != "gz" { | |
| return Err(ErrorKind::BadInstallerType(extension.to_string()).into()); | |
| } | |
| } | |
| None => return Err(ErrorKind::BadInstallerType(String::from("(none)")).into()), | |
| } | |
| // FIXME: Pretty hacky | |
| let is_url = installer_str.starts_with("file://") | |
| || installer_str.starts_with("http://") | |
| || installer_str.starts_with("https://"); | |
| let url = Url::parse(installer_str).ok(); | |
| let url = if is_url { url } else { None }; | |
| if let Some(url) = url { | |
| // Download to a local file | |
| let local_installer = self.cfg.temp_cfg.new_file_with_ext("", ".tar.gz")?; | |
| utils::download_file(&url, &local_installer, None, &|n| { | |
| (self.cfg.notify_handler)(n.into()) | |
| })?; | |
| self.install(InstallMethod::Installer( | |
| &local_installer, | |
| &self.cfg.temp_cfg, | |
| ))?; | |
| } else { | |
| // If installer is a filename | |
| // No need to download | |
| let local_installer = Path::new(installer); | |
| // Install from file | |
| self.install(InstallMethod::Installer( | |
| &local_installer, | |
| &self.cfg.temp_cfg, | |
| ))?; | |
| } | |
| } | |
| Ok(()) | |
| } | |
| pub fn install_from_dir(&self, src: &Path, link: bool) -> Result<()> { | |
| self.ensure_custom()?; | |
| let mut pathbuf = PathBuf::from(src); | |
| pathbuf.push("lib"); | |
| utils::assert_is_directory(&pathbuf)?; | |
| pathbuf.pop(); | |
| pathbuf.push("bin"); | |
| utils::assert_is_directory(&pathbuf)?; | |
| pathbuf.push(format!("rustc{}", EXE_SUFFIX)); | |
| utils::assert_is_file(&pathbuf)?; | |
| if link { | |
| self.install(InstallMethod::Link(&utils::to_absolute(src)?))?; | |
| } else { | |
| self.install(InstallMethod::Copy(src))?; | |
| } | |
| Ok(()) | |
| } | |
| pub fn create_command<T: AsRef<OsStr>>(&self, binary: T) -> Result<Command> { | |
| if !self.exists() { | |
| return Err(ErrorKind::ToolchainNotInstalled(self.name.to_owned()).into()); | |
| } | |
| // Create the path to this binary within the current toolchain sysroot | |
| let binary = if let Some(binary_str) = binary.as_ref().to_str() { | |
| if binary_str.to_lowercase().ends_with(EXE_SUFFIX) { | |
| binary.as_ref().to_owned() | |
| } else { | |
| OsString::from(format!("{}{}", binary_str, EXE_SUFFIX)) | |
| } | |
| } else { | |
| // Very weird case. Non-unicode command. | |
| binary.as_ref().to_owned() | |
| }; | |
| let bin_path = self.path.join("bin").join(&binary); | |
| let path = if utils::is_file(&bin_path) { | |
| &bin_path | |
| } else { | |
| let recursion_count = env::var("RUST_RECURSION_COUNT") | |
| .ok() | |
| .and_then(|s| s.parse().ok()) | |
| .unwrap_or(0); | |
| if recursion_count > env_var::RUST_RECURSION_COUNT_MAX - 1 { | |
| return Err(ErrorKind::BinaryNotFound( | |
| self.name.clone(), | |
| binary.to_string_lossy().into(), | |
| ) | |
| .into()); | |
| } | |
| Path::new(&binary) | |
| }; | |
| let mut cmd = Command::new(&path); | |
| self.set_env(&mut cmd); | |
| Ok(cmd) | |
| } | |
| // Create a command as a fallback for another toolchain. This is used | |
| // to give custom toolchains access to cargo | |
| pub fn create_fallback_command<T: AsRef<OsStr>>( | |
| &self, | |
| binary: T, | |
| primary_toolchain: &Toolchain, | |
| ) -> Result<Command> { | |
| // With the hacks below this only works for cargo atm | |
| assert!(binary.as_ref() == "cargo" || binary.as_ref() == "cargo.exe"); | |
| if !self.exists() { | |
| return Err(ErrorKind::ToolchainNotInstalled(self.name.to_owned()).into()); | |
| } | |
| if !primary_toolchain.exists() { | |
| return Err(ErrorKind::ToolchainNotInstalled(primary_toolchain.name.to_owned()).into()); | |
| } | |
| let src_file = self.path.join("bin").join(format!("cargo{}", EXE_SUFFIX)); | |
| // MAJOR HACKS: Copy cargo.exe to its own directory on windows before | |
| // running it. This is so that the fallback cargo, when it in turn runs | |
| // rustc.exe, will run the rustc.exe out of the PATH environment | |
| // variable, _not_ the rustc.exe sitting in the same directory as the | |
| // fallback. See the `fallback_cargo_calls_correct_rustc` testcase and | |
| // PR 812. | |
| // | |
| // On Windows, spawning a process will search the running application's | |
| // directory for the exe to spawn before searching PATH, and we don't want | |
| // it to do that, because cargo's directory contains the _wrong_ rustc. See | |
| // the documantation for the lpCommandLine argument of CreateProcess. | |
| let exe_path = if cfg!(windows) { | |
| use std::fs; | |
| let fallback_dir = self.cfg.rustup_dir.join("fallback"); | |
| fs::create_dir_all(&fallback_dir) | |
| .chain_err(|| "unable to create dir to hold fallback exe")?; | |
| let fallback_file = fallback_dir.join("cargo.exe"); | |
| if fallback_file.exists() { | |
| fs::remove_file(&fallback_file) | |
| .chain_err(|| "unable to unlink old fallback exe")?; | |
| } | |
| fs::hard_link(&src_file, &fallback_file) | |
| .chain_err(|| "unable to hard link fallback exe")?; | |
| fallback_file | |
| } else { | |
| src_file | |
| }; | |
| let mut cmd = Command::new(exe_path); | |
| self.set_env(&mut cmd); | |
| cmd.env("RUSTUP_TOOLCHAIN", &primary_toolchain.name); | |
| Ok(cmd) | |
| } | |
| fn set_env(&self, cmd: &mut Command) { | |
| self.set_ldpath(cmd); | |
| // Because rustup and cargo use slightly different | |
| // definitions of cargo home (rustup doesn't read HOME on | |
| // windows), we must set it here to ensure cargo and | |
| // rustup agree. | |
| if let Ok(cargo_home) = utils::cargo_home() { | |
| cmd.env("CARGO_HOME", &cargo_home); | |
| } | |
| env_var::inc("RUST_RECURSION_COUNT", cmd); | |
| cmd.env("RUSTUP_TOOLCHAIN", &self.name); | |
| cmd.env("RUSTUP_HOME", &self.cfg.rustup_dir); | |
| } | |
| pub fn set_ldpath(&self, cmd: &mut Command) { | |
| let new_path = self.path.join("lib"); | |
| #[cfg(not(target_os = "macos"))] | |
| mod sysenv { | |
| pub const LOADER_PATH: &'static str = "LD_LIBRARY_PATH"; | |
| } | |
| #[cfg(target_os = "macos")] | |
| mod sysenv { | |
| pub const LOADER_PATH: &'static str = "DYLD_LIBRARY_PATH"; | |
| } | |
| env_var::prepend_path(sysenv::LOADER_PATH, vec![new_path.clone()], cmd); | |
| // Prepend CARGO_HOME/bin to the PATH variable so that we're sure to run | |
| // cargo/rustc via the proxy bins. There is no fallback case for if the | |
| // proxy bins don't exist. We'll just be running whatever happens to | |
| // be on the PATH. | |
| let mut path_entries = vec![]; | |
| if let Ok(cargo_home) = utils::cargo_home() { | |
| path_entries.push(cargo_home.join("bin").to_path_buf()); | |
| } | |
| if cfg!(target_os = "windows") { | |
| path_entries.push(self.path.join("bin")); | |
| } | |
| env_var::prepend_path("PATH", path_entries, cmd); | |
| } | |
| pub fn doc_path(&self, relative: &str) -> Result<PathBuf> { | |
| self.verify()?; | |
| let parts = vec!["share", "doc", "rust", "html"]; | |
| let mut doc_dir = self.path.clone(); | |
| for part in parts { | |
| doc_dir.push(part); | |
| } | |
| doc_dir.push(relative); | |
| Ok(doc_dir) | |
| } | |
| pub fn open_docs(&self, relative: &str) -> Result<()> { | |
| self.verify()?; | |
| Ok(utils::open_browser(&self.doc_path(relative)?)?) | |
| } | |
| pub fn make_default(&self) -> Result<()> { | |
| self.cfg.set_default(&self.name) | |
| } | |
| pub fn make_override(&self, path: &Path) -> Result<()> { | |
| Ok(self.cfg.settings_file.with_mut(|s| { | |
| s.add_override(path, self.name.clone(), self.cfg.notify_handler.as_ref()); | |
| Ok(()) | |
| })?) | |
| } | |
| pub fn list_components(&self) -> Result<Vec<ComponentStatus>> { | |
| if !self.exists() { | |
| return Err(ErrorKind::ToolchainNotInstalled(self.name.to_owned()).into()); | |
| } | |
| let toolchain = &self.name; | |
| let ref toolchain = ToolchainDesc::from_str(toolchain) | |
| .chain_err(|| ErrorKind::ComponentsUnsupported(self.name.to_string()))?; | |
| let prefix = InstallPrefix::from(self.path.to_owned()); | |
| let manifestation = Manifestation::open(prefix, toolchain.target.clone())?; | |
| if let Some(manifest) = manifestation.load_manifest()? { | |
| let config = manifestation.read_config()?; | |
| // Return all optional components of the "rust" package for the | |
| // toolchain's target triple. | |
| let mut res = Vec::new(); | |
| let rust_pkg = manifest | |
| .packages | |
| .get("rust") | |
| .expect("manifest should cantain a rust package"); | |
| let targ_pkg = rust_pkg | |
| .targets | |
| .get(&toolchain.target) | |
| .expect("installed manifest should have a known target"); | |
| for component in &targ_pkg.components { | |
| let installed = config | |
| .as_ref() | |
| .map(|c| c.components.contains(component)) | |
| .unwrap_or(false); | |
| // Get the component so we can check if it is available | |
| let component_pkg = manifest | |
| .get_package(&component.short_name_in_manifest()) | |
| .expect(&format!( | |
| "manifest should contain component {}", | |
| &component.short_name(&manifest) | |
| )); | |
| let component_target_pkg = component_pkg | |
| .targets | |
| .get(&toolchain.target) | |
| .expect("component should have target toolchain"); | |
| res.push(ComponentStatus { | |
| component: component.clone(), | |
| name: component.name(&manifest), | |
| required: true, | |
| installed, | |
| available: component_target_pkg.available(), | |
| }); | |
| } | |
| for extension in &targ_pkg.extensions { | |
| let installed = config | |
| .as_ref() | |
| .map(|c| c.components.contains(extension)) | |
| .unwrap_or(false); | |
| // Get the component so we can check if it is available | |
| let extension_pkg = manifest | |
| .get_package(&extension.short_name_in_manifest()) | |
| .expect(&format!( | |
| "manifest should contain extension {}", | |
| &extension.short_name(&manifest) | |
| )); | |
| let extension_target_pkg = extension_pkg | |
| .targets | |
| .get(&toolchain.target) | |
| .expect("extension should have target toolchain"); | |
| res.push(ComponentStatus { | |
| component: extension.clone(), | |
| name: extension.name(&manifest), | |
| required: false, | |
| installed, | |
| available: extension_target_pkg.available(), | |
| }); | |
| } | |
| res.sort_by(|a, b| a.component.cmp(&b.component)); | |
| Ok(res) | |
| } else { | |
| Err(ErrorKind::ComponentsUnsupported(self.name.to_string()).into()) | |
| } | |
| } | |
| pub fn add_component(&self, component: Component) -> Result<()> { | |
| if self.cfg.telemetry_enabled()? { | |
| return self.telemetry_add_component(component); | |
| } | |
| self.add_component_without_telemetry(component) | |
| } | |
| fn telemetry_add_component(&self, component: Component) -> Result<()> { | |
| let output = self.bare_add_component(component); | |
| match output { | |
| Ok(_) => { | |
| let te = TelemetryEvent::ToolchainUpdate { | |
| toolchain: self.name.to_owned(), | |
| success: true, | |
| }; | |
| match self.telemetry.log_telemetry(te) { | |
| Ok(_) => Ok(()), | |
| Err(e) => { | |
| (self.cfg.notify_handler)(Notification::TelemetryCleanupError(&e)); | |
| Ok(()) | |
| } | |
| } | |
| } | |
| Err(e) => { | |
| let te = TelemetryEvent::ToolchainUpdate { | |
| toolchain: self.name.to_owned(), | |
| success: false, | |
| }; | |
| let _ = self.telemetry.log_telemetry(te).map_err(|xe| { | |
| (self.cfg.notify_handler)(Notification::TelemetryCleanupError(&xe)); | |
| }); | |
| Err(e) | |
| } | |
| } | |
| } | |
| fn add_component_without_telemetry(&self, component: Component) -> Result<()> { | |
| self.bare_add_component(component) | |
| } | |
| fn bare_add_component(&self, mut component: Component) -> Result<()> { | |
| if !self.exists() { | |
| return Err(ErrorKind::ToolchainNotInstalled(self.name.to_owned()).into()); | |
| } | |
| let toolchain = &self.name; | |
| let ref toolchain = ToolchainDesc::from_str(toolchain) | |
| .chain_err(|| ErrorKind::ComponentsUnsupported(self.name.to_string()))?; | |
| let prefix = InstallPrefix::from(self.path.to_owned()); | |
| let manifestation = Manifestation::open(prefix, toolchain.target.clone())?; | |
| if let Some(manifest) = manifestation.load_manifest()? { | |
| // Rename the component if necessary. | |
| if let Some(c) = manifest.rename_component(&component) { | |
| component = c; | |
| } | |
| // Validate the component name | |
| let rust_pkg = manifest | |
| .packages | |
| .get("rust") | |
| .expect("manifest should contain a rust package"); | |
| let targ_pkg = rust_pkg | |
| .targets | |
| .get(&toolchain.target) | |
| .expect("installed manifest should have a known target"); | |
| if targ_pkg.components.contains(&component) { | |
| // Treat it as a warning, see https://github.com/rust-lang/rustup.rs/issues/441 | |
| return Ok(println!( | |
| "{}", | |
| ErrorKind::AddingRequiredComponent( | |
| self.name.to_string(), | |
| component.description(&manifest), | |
| ), | |
| )); | |
| } | |
| if !targ_pkg.extensions.contains(&component) { | |
| let wildcard_component = component.wildcard(); | |
| if targ_pkg.extensions.contains(&wildcard_component) { | |
| component = wildcard_component; | |
| } else { | |
| return Err(ErrorKind::UnknownComponent( | |
| self.name.to_string(), | |
| component.description(&manifest), | |
| ) | |
| .into()); | |
| } | |
| } | |
| let changes = Changes { | |
| add_extensions: vec![component], | |
| remove_extensions: vec![], | |
| }; | |
| manifestation.update( | |
| &manifest, | |
| changes, | |
| false, | |
| &self.download_cfg(), | |
| self.download_cfg().notify_handler.clone(), | |
| )?; | |
| Ok(()) | |
| } else { | |
| Err(ErrorKind::ComponentsUnsupported(self.name.to_string()).into()) | |
| } | |
| } | |
| pub fn remove_component(&self, mut component: Component) -> Result<()> { | |
| if !self.exists() { | |
| return Err(ErrorKind::ToolchainNotInstalled(self.name.to_owned()).into()); | |
| } | |
| let toolchain = &self.name; | |
| let ref toolchain = ToolchainDesc::from_str(toolchain) | |
| .chain_err(|| ErrorKind::ComponentsUnsupported(self.name.to_string()))?; | |
| let prefix = InstallPrefix::from(self.path.to_owned()); | |
| let manifestation = Manifestation::open(prefix, toolchain.target.clone())?; | |
| if let Some(manifest) = manifestation.load_manifest()? { | |
| // Rename the component if necessary. | |
| if let Some(c) = manifest.rename_component(&component) { | |
| component = c; | |
| } | |
| // Validate the component name | |
| let rust_pkg = manifest | |
| .packages | |
| .get("rust") | |
| .expect("manifest should cantain a rust package"); | |
| let targ_pkg = rust_pkg | |
| .targets | |
| .get(&toolchain.target) | |
| .expect("installed manifest should have a known target"); | |
| if targ_pkg.components.contains(&component) { | |
| return Err(ErrorKind::RemovingRequiredComponent( | |
| self.name.to_string(), | |
| component.description(&manifest), | |
| ) | |
| .into()); | |
| } | |
| let dist_config = manifestation.read_config()?.unwrap(); | |
| if !dist_config.components.contains(&component) { | |
| let wildcard_component = component.wildcard(); | |
| if dist_config.components.contains(&wildcard_component) { | |
| component = wildcard_component; | |
| } else { | |
| return Err(ErrorKind::UnknownComponent( | |
| self.name.to_string(), | |
| component.description(&manifest), | |
| ) | |
| .into()); | |
| } | |
| } | |
| let changes = Changes { | |
| add_extensions: vec![], | |
| remove_extensions: vec![component], | |
| }; | |
| manifestation.update( | |
| &manifest, | |
| changes, | |
| false, | |
| &self.download_cfg(), | |
| self.download_cfg().notify_handler.clone(), | |
| )?; | |
| Ok(()) | |
| } else { | |
| Err(ErrorKind::ComponentsUnsupported(self.name.to_string()).into()) | |
| } | |
| } | |
| pub fn binary_file(&self, name: &str) -> PathBuf { | |
| let mut path = self.path.clone(); | |
| path.push("bin"); | |
| path.push(name.to_owned() + env::consts::EXE_SUFFIX); | |
| path | |
| } | |
| } |