From 4e413ea5ec289faede0b3f0b884dbb959dda6295 Mon Sep 17 00:00:00 2001 From: Sebastian Ullrich Date: Thu, 13 Jun 2024 16:49:20 +0200 Subject: [PATCH] Eagerly resolve toolchains to canonical, fixed reference (#106) --- .github/workflows/ci.yml | 10 +- elan-init.sh | 4 +- fetch_nixos_patch.sh | 1 + src/elan-cli/common.rs | 33 ---- src/elan-cli/elan_mode.rs | 88 +++------- src/elan-cli/help.rs | 11 +- src/elan-cli/proxy_mode.rs | 5 +- src/elan-cli/self_update.rs | 34 +--- src/elan-cli/setup_mode.rs | 2 +- src/elan-dist/src/component/package.rs | 17 +- src/elan-dist/src/dist.rs | 234 +++++-------------------- src/elan-dist/src/manifestation.rs | 13 +- src/elan-dist/src/notifications.rs | 6 +- src/elan-utils/src/errors.rs | 5 +- src/elan-utils/src/notifications.rs | 4 +- src/elan-utils/src/utils.rs | 53 ++++-- src/elan/config.rs | 77 ++------ src/elan/errors.rs | 4 + src/elan/install.rs | 23 +-- src/elan/notifications.rs | 2 +- src/elan/settings.rs | 8 +- src/elan/toolchain.rs | 69 +++++--- 22 files changed, 213 insertions(+), 490 deletions(-) create mode 100644 fetch_nixos_patch.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 444dd64..3559bcc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,13 +29,12 @@ jobs: release-target-name: aarch64-unknown-linux-gnu binary-check: true - name: macOS - os: macos-latest + os: macos-13 target: x86_64-apple-darwin binary-check: otool -L - name: macOS aarch64 - os: macos-latest + os: macos-14 target: aarch64-apple-darwin - skip-tests: true binary-check: otool -L - name: Windows os: windows-latest @@ -61,10 +60,11 @@ jobs: target key: ${{ matrix.name }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Setup macOS - if: matrix.os == 'macos-latest' + if: startsWith(matrix.os, 'macos-') shell: bash run: | - brew install coreutils + echo $HOMEBREW_PREFIX/opt/gnu-tar/libexec/gnubin >> $GITHUB_PATH + # still necessary?? echo /usr/local/opt/gnu-tar/libexec/gnubin >> $GITHUB_PATH - name: Build run: | diff --git a/elan-init.sh b/elan-init.sh index e7613fd..e0c7617 100755 --- a/elan-init.sh +++ b/elan-init.sh @@ -34,8 +34,8 @@ FLAGS: -V, --version Prints version information OPTIONS: - --default-toolchain Choose a default toolchain to install - --default-toolchain none Do not install any toolchains + --default-toolchain Choose a default toolchain + --default-toolchain none Do not set a default toolchain EOF } diff --git a/fetch_nixos_patch.sh b/fetch_nixos_patch.sh new file mode 100644 index 0000000..eee5233 --- /dev/null +++ b/fetch_nixos_patch.sh @@ -0,0 +1 @@ +nix build -o nixos.patch $(nix eval nixpkgs#elan.patches --apply "patches: (builtins.elemAt patches 0).outPath" --raw) diff --git a/src/elan-cli/common.rs b/src/elan-cli/common.rs index 17c463a..065d318 100644 --- a/src/elan-cli/common.rs +++ b/src/elan-cli/common.rs @@ -5,7 +5,6 @@ use elan_dist::dist::ToolchainDesc; use elan_utils::notify::NotificationLevel; use elan_utils::utils; use errors::*; -use self_update; use std; use std::io::{BufRead, BufReader, Write}; use std::path::Path; @@ -200,38 +199,6 @@ fn show_channel_updates( Ok(()) } -pub fn update_all_channels(cfg: &Cfg, self_update: bool, force_update: bool) -> Result<()> { - let toolchains = cfg.update_all_channels(force_update)?; - - if toolchains.is_empty() { - info!("no updatable toolchains installed."); - info!("use 'elan toolchain install' to install a toolchain.") - } - - let setup_path = if self_update { - self_update::prepare_update()? - } else { - None - }; - - if !toolchains.is_empty() { - println!(""); - - show_channel_updates(cfg, toolchains)?; - } - - if let Some(ref setup_path) = setup_path { - self_update::run_update(setup_path)?; - - unreachable!(); // update exits on success - } else if self_update { - // Try again in case we emitted "tool `{}` is already installed" last time. - self_update::install_proxies()?; - } - - Ok(()) -} - pub fn lean_version(toolchain: &Toolchain) -> String { if toolchain.exists() { let lean_path = toolchain.binary_file("lean"); diff --git a/src/elan-cli/elan_mode.rs b/src/elan-cli/elan_mode.rs index 8b03c53..7fddf7b 100644 --- a/src/elan-cli/elan_mode.rs +++ b/src/elan-cli/elan_mode.rs @@ -1,6 +1,6 @@ use clap::{App, AppSettings, Arg, ArgMatches, Shell, SubCommand}; use common; -use elan::{command, Cfg, Toolchain}; +use elan::{command, lookup_toolchain_desc, Cfg, Toolchain}; use elan_dist::dist::ToolchainDesc; use elan_utils::utils; use errors::*; @@ -22,12 +22,11 @@ pub fn main() -> Result<()> { match matches.subcommand() { ("show", Some(_)) => show(cfg)?, - ("install", Some(m)) => update(cfg, m)?, - ("update", Some(m)) => update(cfg, m)?, + ("install", Some(m)) => install(cfg, m)?, ("uninstall", Some(m)) => toolchain_remove(cfg, m)?, ("default", Some(m)) => default_(cfg, m)?, ("toolchain", Some(c)) => match c.subcommand() { - ("install", Some(m)) => update(cfg, m)?, + ("install", Some(m)) => install(cfg, m)?, ("list", Some(_)) => common::list_toolchains(cfg)?, ("link", Some(m)) => toolchain_link(cfg, m)?, ("uninstall", Some(m)) => toolchain_remove(cfg, m)?, @@ -79,7 +78,7 @@ pub fn cli() -> App<'static, 'static> { .about("Show the active and installed toolchains") .after_help(SHOW_HELP)) .subcommand(SubCommand::with_name("install") - .about("Update Lean toolchains") + .about("Install Lean toolchain") .after_help(INSTALL_HELP) .setting(AppSettings::Hidden) // synonym for 'toolchain install' .arg(Arg::with_name("toolchain") @@ -93,22 +92,6 @@ pub fn cli() -> App<'static, 'static> { .help(TOOLCHAIN_ARG_HELP) .required(true) .multiple(true))) - .subcommand(SubCommand::with_name("update") - .about("Update Lean toolchains and elan") - .after_help(UPDATE_HELP) - .arg(Arg::with_name("toolchain") - .help(TOOLCHAIN_ARG_HELP) - .required(false) - .multiple(true)) - .arg(Arg::with_name("no-self-update") - .help("Don't perform self update when running the `elan` command") - .long("no-self-update") - .takes_value(false) - .hidden(true)) - .arg(Arg::with_name("force") - .help("Force an update, even if some components are missing") - .long("force") - .takes_value(false))) .subcommand(SubCommand::with_name("default") .about("Set the default toolchain") .after_help(DEFAULT_HELP) @@ -124,8 +107,7 @@ pub fn cli() -> App<'static, 'static> { .subcommand(SubCommand::with_name("list") .about("List installed toolchains")) .subcommand(SubCommand::with_name("install") - .about("Install or update a given toolchain") - .aliases(&["update", "add"]) + .about("Install a given toolchain") .arg(Arg::with_name("toolchain") .help(TOOLCHAIN_ARG_HELP) .required(true) @@ -249,8 +231,8 @@ pub fn cli() -> App<'static, 'static> { } fn default_(cfg: &Cfg, m: &ArgMatches) -> Result<()> { - let ref toolchain = m.value_of("toolchain").expect(""); - let toolchain = ToolchainDesc::from_str(toolchain)?; + let ref name = m.value_of("toolchain").expect(""); + let toolchain = lookup_toolchain_desc(cfg, name)?; let ref toolchain = cfg.get_toolchain(&toolchain, false)?; let status = if !toolchain.exists() || !toolchain.is_custom() { @@ -259,7 +241,7 @@ fn default_(cfg: &Cfg, m: &ArgMatches) -> Result<()> { None }; - toolchain.make_default()?; + cfg.set_default(name)?; if let Some(status) = status { println!(""); @@ -269,29 +251,22 @@ fn default_(cfg: &Cfg, m: &ArgMatches) -> Result<()> { Ok(()) } -fn update(cfg: &Cfg, m: &ArgMatches) -> Result<()> { - if let Some(names) = m.values_of("toolchain") { - for name in names { - let desc = ToolchainDesc::from_str(name)?; - let toolchain = cfg.get_toolchain(&desc, false)?; - - let status = if !toolchain.exists() || !toolchain.is_custom() { - Some(toolchain.install_from_dist(m.is_present("force"))?) - } else { - None - }; - - if let Some(status) = status { - println!(""); - common::show_channel_update(cfg, &toolchain.desc, Ok(status))?; - } +fn install(cfg: &Cfg, m: &ArgMatches) -> Result<()> { + let names = m.values_of("toolchain").expect(""); + for name in names { + let desc = lookup_toolchain_desc(cfg, name)?; + let toolchain = cfg.get_toolchain(&desc, false)?; + + let status = if !toolchain.exists() || !toolchain.is_custom() { + Some(toolchain.install_from_dist()?) + } else { + None + }; + + if let Some(status) = status { + println!(""); + common::show_channel_update(cfg, &toolchain.desc, Ok(status))?; } - } else { - common::update_all_channels( - cfg, - !m.is_present("no-self-update") && !elan::install::NEVER_SELF_UPDATE, - m.is_present("force"), - )?; } Ok(()) @@ -301,7 +276,7 @@ fn run(cfg: &Cfg, m: &ArgMatches) -> Result<()> { let ref toolchain = m.value_of("toolchain").expect(""); let args = m.values_of("command").unwrap(); let args: Vec<_> = args.collect(); - let desc = ToolchainDesc::from_str(toolchain)?; + let desc = lookup_toolchain_desc(cfg, toolchain)?; let cmd = cfg.create_command_for_toolchain(&desc, m.is_present("install"), args[0])?; Ok(command::run_command_for_dir( @@ -344,13 +319,8 @@ fn show(cfg: &Cfg) -> Result<()> { if show_headers { print_header("installed toolchains") } - let default_name = cfg.get_default()?; for t in installed_toolchains { - if default_name.as_ref() == Some(&t) { - println!("{} (default)", t); - } else { - println!("{}", t); - } + println!("{}", t); } if show_headers { println!("") @@ -405,7 +375,7 @@ fn show(cfg: &Cfg) -> Result<()> { fn explicit_or_dir_toolchain<'a>(cfg: &'a Cfg, m: &ArgMatches) -> Result> { let toolchain = m.value_of("toolchain"); if let Some(toolchain) = toolchain { - let desc = ToolchainDesc::from_str(toolchain)?; + let desc = lookup_toolchain_desc(cfg, toolchain)?; let toolchain = cfg.get_toolchain(&desc, false)?; return Ok(toolchain); } @@ -419,7 +389,7 @@ fn explicit_or_dir_toolchain<'a>(cfg: &'a Cfg, m: &ArgMatches) -> Result Result<()> { let ref toolchain = m.value_of("toolchain").expect(""); let ref path = m.value_of("path").expect(""); - let desc = ToolchainDesc::from_str(toolchain)?; + let desc = ToolchainDesc::from_resolved_str(toolchain)?; let toolchain = cfg.get_toolchain(&desc, true)?; Ok(toolchain.install_from_dir(Path::new(path), true)?) @@ -427,7 +397,7 @@ fn toolchain_link(cfg: &Cfg, m: &ArgMatches) -> Result<()> { fn toolchain_remove(cfg: &Cfg, m: &ArgMatches) -> Result<()> { for toolchain in m.values_of("toolchain").expect("") { - let desc = ToolchainDesc::from_str(toolchain)?; + let desc = lookup_toolchain_desc(cfg, toolchain)?; let toolchain = cfg.get_toolchain(&desc, false)?; toolchain.remove()?; } @@ -436,7 +406,7 @@ fn toolchain_remove(cfg: &Cfg, m: &ArgMatches) -> Result<()> { fn override_add(cfg: &Cfg, m: &ArgMatches) -> Result<()> { let ref toolchain = m.value_of("toolchain").expect(""); - let desc = ToolchainDesc::from_str(toolchain)?; + let desc = lookup_toolchain_desc(cfg, toolchain)?; let toolchain = cfg.get_toolchain(&desc, false)?; let status = if !toolchain.exists() || !toolchain.is_custom() { diff --git a/src/elan-cli/help.rs b/src/elan-cli/help.rs index ccf4d05..62ef536 100644 --- a/src/elan-cli/help.rs +++ b/src/elan-cli/help.rs @@ -12,22 +12,13 @@ pub static SHOW_HELP: &'static str = r"DISCUSSION: If there are multiple toolchains installed then all installed toolchains are listed as well."; -pub static UPDATE_HELP: &'static str = r"DISCUSSION: - With no toolchain specified, the `update` command updates each of - the installed toolchains from the official release channels, then - updates elan itself. - - If given a toolchain argument then `update` updates that - toolchain, the same as `elan toolchain install`."; - pub static INSTALL_HELP: &'static str = r"DISCUSSION: Installs a specific lean toolchain. The 'install' command is an alias for 'elan update '."; pub static DEFAULT_HELP: &'static str = r"DISCUSSION: - Sets the default toolchain to the one specified. If the toolchain - is not already installed then it is installed first."; + Sets the default toolchain to the one specified."; pub static TOOLCHAIN_HELP: &'static str = r"DISCUSSION: Many `elan` commands deal with *toolchains*, a single diff --git a/src/elan-cli/proxy_mode.rs b/src/elan-cli/proxy_mode.rs index 22283d2..fd45829 100644 --- a/src/elan-cli/proxy_mode.rs +++ b/src/elan-cli/proxy_mode.rs @@ -1,8 +1,7 @@ use common::set_globals; use elan::command::run_command_for_dir; -use elan::Cfg; +use elan::{lookup_toolchain_desc, Cfg}; use elan_utils::utils; -use elan_dist::dist::ToolchainDesc; use errors::*; use job; use std::env; @@ -50,7 +49,7 @@ fn direct_proxy(cfg: &Cfg, arg0: &str, toolchain: Option<&str>, args: &[OsString let cmd = match toolchain { None => cfg.create_command_for_dir(&utils::current_dir()?, arg0)?, Some(tc) => { - let desc = ToolchainDesc::from_str(tc)?; + let desc = lookup_toolchain_desc(cfg, tc)?; cfg.create_command_for_toolchain(&desc, true, arg0)? } }; diff --git a/src/elan-cli/self_update.rs b/src/elan-cli/self_update.rs index b71f145..dccdbbe 100644 --- a/src/elan-cli/self_update.rs +++ b/src/elan-cli/self_update.rs @@ -31,8 +31,8 @@ //! and racy on Windows. use common::{self, Confirm}; +use elan::lookup_toolchain_desc; use elan_dist::dist; -use elan_dist::dist::ToolchainDesc; use elan_utils::utils; use errors::*; use flate2; @@ -232,7 +232,12 @@ pub fn install(no_prompt: bool, verbose: bool, mut opts: InstallOpts) -> Result< if !opts.no_modify_path { do_add_to_path(&get_add_path_methods())?; } - maybe_install_lean(&opts.default_toolchain, verbose)?; + if opts.default_toolchain != "none" { + let ref cfg = common::set_globals(verbose)?; + // sanity-check reference + let _ = lookup_toolchain_desc(cfg, &opts.default_toolchain)?; + cfg.set_default(&opts.default_toolchain)?; + } if cfg!(unix) { let ref env_file = utils::elan_home()?.join("env"); @@ -533,31 +538,6 @@ pub fn install_proxies() -> Result<()> { Ok(()) } -fn maybe_install_lean(toolchain_str: &str, verbose: bool) -> Result<()> { - let ref cfg = common::set_globals(verbose)?; - - // If there is already an install, then `toolchain_str` may not be - // a toolchain the user actually wants. Don't do anything. FIXME: - // This logic should be part of InstallOpts so that it isn't - // possible to select a toolchain then have it not be installed. - if toolchain_str == "none" { - info!("skipping toolchain installation"); - println!(""); - } else if cfg.find_default()?.is_none() { - let desc = ToolchainDesc::from_str(toolchain_str)?; - let toolchain = cfg.get_toolchain(&desc, false)?; - let status = toolchain.install_from_dist(false)?; - cfg.set_default(&desc)?; - println!(""); - common::show_channel_update(cfg, &desc, Ok(status))?; - } else { - info!("updating existing elan installation"); - println!(""); - } - - Ok(()) -} - pub fn uninstall(no_prompt: bool) -> Result<()> { if elan::install::NEVER_SELF_UPDATE { err!("self-uninstall is disabled for this build of elan"); diff --git a/src/elan-cli/setup_mode.rs b/src/elan-cli/setup_mode.rs index b8b3872..b31d3e1 100644 --- a/src/elan-cli/setup_mode.rs +++ b/src/elan-cli/setup_mode.rs @@ -32,7 +32,7 @@ pub fn main() -> Result<()> { Arg::with_name("default-toolchain") .long("default-toolchain") .takes_value(true) - .help("Choose a default toolchain to install"), + .help("Choose a default toolchain"), ) .arg( Arg::with_name("no-modify-path") diff --git a/src/elan-dist/src/component/package.rs b/src/elan-dist/src/component/package.rs index 636e6e5..e7047e0 100644 --- a/src/elan-dist/src/component/package.rs +++ b/src/elan-dist/src/component/package.rs @@ -8,7 +8,6 @@ extern crate zstd; extern crate tar; use errors::*; -use temp; use std::fs::{self, File}; use std::io::{self, Read, Seek}; @@ -17,9 +16,9 @@ use std::path::{Path, PathBuf}; use zip::ZipArchive; #[derive(Debug)] -pub struct TarPackage<'a>(temp::Dir<'a>); +pub struct TarPackage(); -impl<'a> TarPackage<'a> { +impl TarPackage { pub fn unpack(stream: R, path: &Path) -> Result<()> { let mut archive = tar::Archive::new(stream); // The lean-installer packages unpack to a directory called @@ -62,9 +61,9 @@ fn unpack_without_first_dir(archive: &mut tar::Archive, path: &Path) } #[derive(Debug)] -pub struct ZipPackage<'a>(temp::Dir<'a>); +pub struct ZipPackage(); -impl<'a> ZipPackage<'a> { +impl ZipPackage { pub fn unpack(stream: R, path: &Path) -> Result<()> { let mut archive = ZipArchive::new(stream).chain_err(|| ErrorKind::ExtractingPackage)?; /* @@ -132,9 +131,9 @@ impl<'a> ZipPackage<'a> { } #[derive(Debug)] -pub struct TarGzPackage<'a>(TarPackage<'a>); +pub struct TarGzPackage(); -impl<'a> TarGzPackage<'a> { +impl TarGzPackage { pub fn unpack(stream: R, path: &Path) -> Result<()> { let stream = flate2::read::GzDecoder::new(stream); @@ -147,9 +146,9 @@ impl<'a> TarGzPackage<'a> { } #[derive(Debug)] -pub struct TarZstdPackage<'a>(TarPackage<'a>); +pub struct TarZstdPackage(); -impl<'a> TarZstdPackage<'a> { +impl TarZstdPackage { pub fn unpack(stream: R, path: &Path) -> Result<()> { let stream = zstd::stream::read::Decoder::new(stream)?; diff --git a/src/elan-dist/src/dist.rs b/src/elan-dist/src/dist.rs index 968c222..4c91c0f 100644 --- a/src/elan-dist/src/dist.rs +++ b/src/elan-dist/src/dist.rs @@ -1,209 +1,74 @@ use download::DownloadCfg; -use elan_utils::utils::fetch_url; use elan_utils::{self, utils}; use errors::*; -use manifest::Component; use manifestation::Manifestation; -use notifications::Notification; use prefix::InstallPrefix; -use temp; - -use std::fmt; -use std::path::Path; - use regex::Regex; -const DEFAULT_ORIGIN: &str = "leanprover/lean4"; +use std::fmt; // Fully-resolved toolchain descriptors. These always have full target // triples attached to them and are used for canonical identification, // such as naming their installation directory. #[derive(Debug, Clone, PartialEq)] -pub struct ToolchainDesc { - // The GitHub source repository to use (if "nightly" is specified, we append "-nightly" to this) - // If None, we default to "leanprover/lean" - pub origin: Option, - // Either "nightly", "stable", an explicit version number, or a tag name - pub channel: String, - pub date: Option, +pub enum ToolchainDesc { + // A linked toolchain + Local { name: String }, + Remote { + // The GitHub source repository to use (if "nightly" is specified, we append "-nightly" to this). + origin: String, + // The release name, usually a Git tag + release: String, + } } impl ToolchainDesc { - pub fn from_str(name: &str) -> Result { - let pattern = r"^(?:([a-zA-Z0-9-]+[/][a-zA-Z0-9-]+)[:])?(?:(nightly|stable)(?:-(\d{4}-\d{2}-\d{2}))?|([a-zA-Z0-9-.]+))$"; + pub fn from_resolved_str(name: &str) -> Result { + let pattern = r"^(?:([a-zA-Z0-9-]+[/][a-zA-Z0-9-]+)[:])?([a-zA-Z0-9-.]+)$"; let re = Regex::new(&pattern).unwrap(); if let Some(c) = re.captures(name) { - fn fn_map(s: &str) -> Option { - if s == "" { - None - } else { - Some(s.to_owned()) + match c.get(1) { + Some(origin) => { + let origin = origin.as_str().to_owned(); + let release = c.get(2).unwrap().as_str().to_owned(); + Ok(ToolchainDesc::Remote { origin, release }) + } + None => { + let name = c.get(2).unwrap().as_str().to_owned(); + Ok(ToolchainDesc::Local { name }) } } - let origin = c.get(1).map(|s| s.as_str()).and_then(fn_map); - let tag = c.get(4).map(|m| m.as_str()); - if let (Some(ref origin), Some("lean-toolchain")) = (&origin, tag) { - let toolchain_url = format!("https://raw.githubusercontent.com/{}/HEAD/lean-toolchain", origin); - return ToolchainDesc::from_str(fetch_url(&toolchain_url)?.trim()) - } - - Ok(ToolchainDesc { - origin, - channel: c.get(2).map(|s| s.as_str().to_owned()).or(tag.map(|t| t.to_owned())).unwrap(), - date: c.get(3).map(|s| s.as_str()).and_then(fn_map), - }) } else { Err(ErrorKind::InvalidToolchainName(name.to_string()).into()) } } - - /// Either "$channel" or "channel-$date" - pub fn manifest_name(&self) -> String { - match self.date { - None => self.channel.clone(), - Some(ref date) => format!("{}-{}", self.channel, date), - } - } - - pub fn package_dir(&self, dist_root: &str) -> String { - match self.date { - None => format!("{}", dist_root), - Some(ref date) => format!("{}/{}", dist_root, date), - } - } - - pub fn full_spec(&self) -> String { - if self.date.is_some() { - format!("{}", self) - } else { - format!("{} (tracking)", self) - } - } - - pub fn is_tracking(&self) -> bool { - let channels = ["nightly", "stable"]; - channels.iter().any(|x| *x == self.channel) && self.date.is_none() - } } -#[derive(Debug)] -pub struct Manifest<'a>(temp::File<'a>, String); - impl fmt::Display for ToolchainDesc { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(ref origin) = self.origin { - write!(f, "{}:", origin)?; - } - - write!(f, "{}", &self.channel)?; - - if let Some(ref date) = self.date { - write!(f, "-{}", date)?; + match self { + ToolchainDesc::Local { name } => write!(f, "{}", name), + ToolchainDesc::Remote { origin, release } => + write!(f, "{}:{}", origin, release) } - - Ok(()) } } -// Installs or updates a toolchain from a dist server. If an initial -// install then it will be installed with the default components. If -// an upgrade then all the existing components will be upgraded. -// -// Returns the manifest's hash if anything changed. -pub fn update_from_dist<'a>( +pub fn install_from_dist<'a>( download: DownloadCfg<'a>, - update_hash: Option<&Path>, toolchain: &ToolchainDesc, prefix: &InstallPrefix, - add: &[Component], - remove: &[Component], - force_update: bool, -) -> Result> { - let fresh_install = !prefix.path().exists(); - - let res = update_from_dist_( - download, - update_hash, - toolchain, - prefix, - add, - remove, - force_update, - ); - - // Don't leave behind an empty / broken installation directory - if res.is_err() && fresh_install { - // FIXME Ignoring cascading errors - let _ = utils::remove_dir("toolchain", prefix.path(), &|n| { - (download.notify_handler)(n.into()) - }); - } - - res -} - -//Append "-nightly" to the origin if version == "nightly" was specified. -//If origin is None use DEFAULT_ORIGIN. -fn build_origin_name(origin: Option<&String>, version: &str) -> String { - let repo = match origin { - None => DEFAULT_ORIGIN, - Some(repo) => repo, - }; - format!( - "{}{}", - repo, - if version == "nightly" { "-nightly" } else { "" } - ) -} - -pub fn update_from_dist_<'a>( - download: DownloadCfg<'a>, - update_hash: Option<&Path>, - toolchain: &ToolchainDesc, - prefix: &InstallPrefix, - _add: &[Component], - _remove: &[Component], - _force_update: bool, -) -> Result> { +) -> Result<()> { let toolchain_str = toolchain.to_string(); let manifestation = Manifestation::open(prefix.clone())?; - let url = match toolchain_url(download, toolchain) { - Ok(url) => url, - Err(Error(ErrorKind::Utils(elan_utils::ErrorKind::DownloadNotExists { .. }), _)) => { - return Err(format!("no release found for '{}'", toolchain.manifest_name()).into()); - } - Err(e @ Error(ErrorKind::ChecksumFailed { .. }, _)) => { - return Err(e); - } - Err(e) => { - return Err(e).chain_err(|| { - format!( - "failed to resolve latest version of '{}'", - toolchain.manifest_name() - ) - }); - } + let ToolchainDesc::Remote { origin, release } = toolchain else { + return Ok(()) }; - - if let Some(hash_file) = update_hash { - if utils::is_file(hash_file) { - if let Ok(contents) = utils::read_file("update hash", hash_file) { - if contents == url { - // Skip download, url matches - return Ok(None); - } - } /*else { - (self.notify_handler)(Notification::CantReadUpdateHash(hash_file)); - }*/ - } /*else { - (self.notify_handler)(Notification::NoUpdateHash(hash_file)); - }*/ - } - - match manifestation.update( - &build_origin_name(toolchain.origin.as_ref(), &toolchain.channel), + let url = format!("https://github.com/{}/releases/expanded_assets/{}", origin, release); + let res = match manifestation.install( + &origin, &url, &download.temp_cfg, download.notify_handler.clone(), @@ -217,34 +82,17 @@ pub fn update_from_dist_<'a>( ) }), Err(e) => Err(e), + }; + + // Don't leave behind an empty / broken installation directory + if res.is_err() { + // FIXME Ignoring cascading errors + let _ = utils::remove_dir("toolchain", prefix.path(), &|n| { + (download.notify_handler)(n.into()) + }); } - .map(|()| Some(url)) -} -fn toolchain_url<'a>(download: DownloadCfg<'a>, toolchain: &ToolchainDesc) -> Result { - let origin = build_origin_name(toolchain.origin.as_ref(), toolchain.channel.as_ref()); - Ok( - match (toolchain.date.as_ref(), toolchain.channel.as_str()) { - (None, version) if version == "stable" || version == "nightly" => { - (download.notify_handler)(Notification::DownloadingManifest(version)); - let release = utils::fetch_latest_release_tag(&origin)?; - (download.notify_handler)(Notification::DownloadedManifest( - version, - Some(&release), - )); - format!("https://github.com/{}/releases/expanded_assets/{}", origin, release) - } - (Some(date), "nightly") => format!( - "https://github.com/{}/releases/expanded_assets/nightly-{}", - origin, date - ), - (None, version) if version.starts_with(|c: char| c.is_numeric()) => { - format!("https://github.com/{}/releases/expanded_assets/v{}", origin, version) - } - (None, tag) => format!("https://github.com/{}/releases/expanded_assets/{}", origin, tag), - _ => panic!("wat"), - }, - ) + res } pub fn host_triple() -> &'static str { diff --git a/src/elan-dist/src/manifestation.rs b/src/elan-dist/src/manifestation.rs index 67f2859..46e9e30 100644 --- a/src/elan-dist/src/manifestation.rs +++ b/src/elan-dist/src/manifestation.rs @@ -18,16 +18,13 @@ impl Manifestation { Ok(Manifestation { prefix }) } - /// Installation using the legacy v1 manifest format - pub fn update( + pub fn install( &self, origin: &String, url: &String, temp_cfg: &temp::Cfg, notify_handler: &dyn Fn(Notification), ) -> Result<()> { - notify_handler(Notification::DownloadingComponent("lean")); - let dlcfg = DownloadCfg { temp_cfg: temp_cfg, notify_handler: notify_handler, @@ -69,21 +66,19 @@ impl Manifestation { ); } let url = format!("https://github.com/{}", url.unwrap()); + notify_handler(Notification::DownloadingComponent(&url)); let installer_file = dlcfg.download_and_check(&url)?; let prefix = self.prefix.path(); - notify_handler(Notification::InstallingComponent("lean")); + notify_handler(Notification::InstallingComponent(&prefix.to_string_lossy())); // unpack into temporary place, then move atomically to guard against aborts during unpacking let unpack_dir = prefix.with_extension("tmp"); - // Remove old files if utils::is_directory(prefix) { - utils::remove_dir("toolchain directory", prefix, &|n| { - (notify_handler)(n.into()) - })?; + return Err(format!("'{}' is already installed", prefix.display()).into()) } if utils::is_directory(&unpack_dir) { diff --git a/src/elan-dist/src/notifications.rs b/src/elan-dist/src/notifications.rs index e508a39..e07b029 100644 --- a/src/elan-dist/src/notifications.rs +++ b/src/elan-dist/src/notifications.rs @@ -105,9 +105,9 @@ impl<'a> Display for Notification<'a> { MissingInstalledComponent(c) => { write!(f, "during uninstall component {} was not found", c) } - DownloadingComponent(c) => write!(f, "downloading component '{}'", c), - InstallingComponent(c) => write!(f, "installing component '{}'", c), - RemovingComponent(c) => write!(f, "removing component '{}'", c), + DownloadingComponent(c) => write!(f, "downloading {}", c), + InstallingComponent(c) => write!(f, "installing {}", c), + RemovingComponent(c) => write!(f, "removing {}", c), DownloadingManifest(t) => write!(f, "syncing channel updates for '{}'", t), DownloadedManifest(date, Some(version)) => { write!(f, "latest update on {}, lean version {}", date, version) diff --git a/src/elan-utils/src/errors.rs b/src/elan-utils/src/errors.rs index 43b7de2..7ac5d18 100644 --- a/src/elan-utils/src/errors.rs +++ b/src/elan-utils/src/errors.rs @@ -1,5 +1,6 @@ use download; use std::ffi::OsString; +use std::io; use std::path::PathBuf; use url::Url; @@ -8,7 +9,9 @@ error_chain! { Download(download::Error, download::ErrorKind); } - foreign_links { } + foreign_links { + Io(io::Error); + } errors { LocatingWorkingDir { diff --git a/src/elan-utils/src/notifications.rs b/src/elan-utils/src/notifications.rs index 4f07ccb..b2a10fc 100644 --- a/src/elan-utils/src/notifications.rs +++ b/src/elan-utils/src/notifications.rs @@ -23,6 +23,7 @@ pub enum Notification<'a> { UsingCurl, UsingReqwest, UsingHyperDeprecated, + UsingCachedRelease(&'a str), } impl<'a> Notification<'a> { @@ -39,7 +40,7 @@ impl<'a> Notification<'a> { | ResumingPartialDownload | UsingCurl | UsingReqwest => NotificationLevel::Verbose, - UsingHyperDeprecated | NoCanonicalPath(_) => NotificationLevel::Warn, + UsingHyperDeprecated | NoCanonicalPath(_) | UsingCachedRelease(_) => NotificationLevel::Warn, } } } @@ -67,6 +68,7 @@ impl<'a> Display for Notification<'a> { UsingHyperDeprecated => f.write_str( "ELAN_USE_HYPER environment variable is deprecated, use ELAN_USE_REQWEST instead", ), + UsingCachedRelease(tag) => write!(f, "failed to query latest release, using cached version '{}'", tag), } } } diff --git a/src/elan-utils/src/utils.rs b/src/elan-utils/src/utils.rs index 332bd48..70ce21d 100644 --- a/src/elan-utils/src/utils.rs +++ b/src/elan-utils/src/utils.rs @@ -457,35 +457,50 @@ pub fn toolchain_sort>(v: &mut Vec) { pub fn fetch_url(url: &str) -> Result { let mut data = Vec::new(); - ::download::curl::EASY.with(|handle| { + ::download::curl::EASY.with::<_, Result<()>>(|handle| { let mut handle = handle.borrow_mut(); handle.url(url).unwrap(); handle.follow_location(true).unwrap(); - { - let mut transfer = handle.transfer(); - transfer - .write_function(|new_data| { - data.extend_from_slice(new_data); - Ok(new_data.len()) - }) - .unwrap(); - transfer.perform().unwrap(); - } - }); + let mut transfer = handle.transfer(); + transfer + .write_function(|new_data| { + data.extend_from_slice(new_data); + Ok(new_data.len()) + }) + .unwrap(); + transfer.perform().chain_err(|| "error during download") + })?; ::std::str::from_utf8(&data).chain_err(|| "failed to decode response").map(|s| s.to_owned()) } // fetch from HTML page instead of Github API to avoid rate limit -pub fn fetch_latest_release_tag(repo_slug: &str) -> Result { +pub fn fetch_latest_release_tag(repo_slug: &str, notify_handler: Option<&dyn Fn(Notification)>) -> Result { use regex::Regex; let latest_url = format!("https://github.com/{}/releases/latest", repo_slug); - let redirect = fetch_url(&latest_url)?; - let re = Regex::new(r#"/tag/([-a-z0-9.]+)"#).unwrap(); - let capture = re.captures(&redirect); - match capture { - Some(cap) => Ok(cap.get(1).unwrap().as_str().to_string()), - None => Err("failed to parse latest release tag".into()), + let cache_path = elan_home()?.join("cached-tags").join(repo_slug); + match fetch_url(&latest_url) { + Ok(redirect) => { + let re = Regex::new(r#"/tag/([-a-z0-9.]+)"#).unwrap(); + let capture = re.captures(&redirect); + let tag = match capture { + Some(cap) => cap.get(1).unwrap().as_str().to_string(), + None => return Err("failed to parse latest release tag".into()), + }; + fs::create_dir_all(cache_path.parent().unwrap())?; + fs::write(cache_path, &tag)?; + Ok(tag) + } + Err(e) => { + if let Some(handler) = notify_handler { + if cache_path.exists() { + let tag = fs::read_to_string(cache_path)?; + handler(Notification::UsingCachedRelease(&tag)); + return Ok(tag) + } + } + Err(e) + } } } diff --git a/src/elan/config.rs b/src/elan/config.rs index ffb05be..04d8616 100644 --- a/src/elan/config.rs +++ b/src/elan/config.rs @@ -11,10 +11,12 @@ use elan_utils::utils; use errors::*; use notifications::*; use settings::{Settings, SettingsFile}; -use toolchain::{Toolchain, UpdateStatus}; +use toolchain::Toolchain; use toml; +use crate::lookup_toolchain_desc; + #[derive(Debug)] pub enum OverrideReason { Environment, @@ -52,7 +54,6 @@ pub struct Cfg { pub elan_dir: PathBuf, pub settings_file: SettingsFile, pub toolchains_dir: PathBuf, - pub update_hash_dir: PathBuf, pub temp_cfg: temp::Cfg, //pub gpg_key: Cow<'static, str>, pub env_override: Option, @@ -69,7 +70,6 @@ impl Cfg { let settings_file = SettingsFile::new(elan_dir.join("settings.toml")); let toolchains_dir = elan_dir.join("toolchains"); - let update_hash_dir = elan_dir.join("update-hashes"); // GPG key /*let gpg_key = ""; if let Some(path) = env::var_os("ELAN_GPG_KEY") @@ -94,7 +94,6 @@ impl Cfg { elan_dir: elan_dir, settings_file: settings_file, toolchains_dir: toolchains_dir, - update_hash_dir: update_hash_dir, temp_cfg: temp_cfg, //gpg_key: gpg_key, notify_handler: notify_handler, @@ -102,7 +101,7 @@ impl Cfg { }) } - pub fn set_default(&self, toolchain: &ToolchainDesc) -> Result<()> { + pub fn set_default(&self, toolchain: &str) -> Result<()> { self.settings_file.with_mut(|s| { s.default_toolchain = Some(toolchain.to_owned()); Ok(()) @@ -118,25 +117,15 @@ impl Cfg { })?; } - Toolchain::from(self, name) + Ok(Toolchain::from(self, name)) } pub fn verify_toolchain(&self, name: &ToolchainDesc) -> Result { let toolchain = self.get_toolchain(name, false)?; - toolchain.verify()?; + toolchain.install_from_dist_if_not_installed()?; Ok(toolchain) } - pub fn get_hash_file(&self, toolchain: &str, create_parent: bool) -> Result { - if create_parent { - utils::ensure_dir_exists("update-hash", &self.update_hash_dir, &|n| { - (self.notify_handler)(n.into()) - })?; - } - - Ok(self.update_hash_dir.join(toolchain)) - } - pub fn which_binary(&self, path: &Path, binary: &str) -> Result> { if let Some((toolchain, _)) = self.find_override_toolchain_or_default(path)? { Ok(Some(toolchain.binary_file(binary))) @@ -151,10 +140,7 @@ impl Cfg { .with(|s| Ok(s.default_toolchain.clone()))?; if let Some(name) = opt_name { - let toolchain = self - .verify_toolchain(&name) - .chain_err(|| ErrorKind::ToolchainNotInstalled(name))?; - + let toolchain = self.verify_toolchain(&lookup_toolchain_desc(&self, &name)?)?; Ok(Some(toolchain)) } else { Ok(None) @@ -166,7 +152,7 @@ impl Cfg { // First check ELAN_TOOLCHAIN if let Some(ref name) = self.env_override { - override_ = Some((ToolchainDesc::from_str(name)?, OverrideReason::Environment)); + override_ = Some((lookup_toolchain_desc(&self, name)?, OverrideReason::Environment)); } // Then walk up the directory tree from 'path' looking for either the @@ -220,7 +206,7 @@ impl Cfg { if toolchain.exists() { Ok(Some((toolchain, reason))) } else { - toolchain.install_from_dist(false)?; + toolchain.install_from_dist()?; Ok(Some((toolchain, reason))) } } @@ -254,7 +240,7 @@ impl Cfg { if let Ok(s) = utils::read_file("toolchain file", &toolchain_file) { if let Some(s) = s.lines().next() { let toolchain_name = s.trim(); - let desc = ToolchainDesc::from_str(toolchain_name)?; + let desc = lookup_toolchain_desc(&self, toolchain_name)?; let reason = OverrideReason::ToolchainFile(toolchain_file); return Ok(Some((desc, reason))); } @@ -272,7 +258,7 @@ impl Cfg { { None => {} Some(toml::Value::String(s)) => { - let desc = ToolchainDesc::from_str(s)?; + let desc = lookup_toolchain_desc(&self, s)?; return Ok(Some((desc, OverrideReason::LeanpkgFile(leanpkg_file)))) } Some(a) => { @@ -287,7 +273,7 @@ impl Cfg { if let Some(last) = d.file_name() { if let Some(last) = last.to_str() { return Ok(Some(( - ToolchainDesc::from_str(last)?, + lookup_toolchain_desc(&self, last)?, OverrideReason::InToolchainDirectory(d.into()), ))); } @@ -311,10 +297,6 @@ impl Cfg { ) } - pub fn get_default(&self) -> Result> { - self.settings_file.with(|s| Ok(s.default_toolchain.clone())) - } - pub fn list_toolchains(&self) -> Result> { // de-sanitize toolchain file names (best effort...) fn insane(s: String) -> String { @@ -330,43 +312,14 @@ impl Cfg { utils::toolchain_sort(&mut toolchains); - let toolchains: Vec<_> = toolchains.iter().map(|s| ToolchainDesc::from_str(&s)).collect::>>()?; + // ignore legacy toolchains in non-resolved format + let toolchains: Vec<_> = toolchains.iter().flat_map(|s| ToolchainDesc::from_resolved_str(&s)).collect(); Ok(toolchains) } else { Ok(Vec::new()) } } - pub fn update_all_channels( - &self, - force_update: bool, - ) -> Result)>> { - let toolchains = self.list_toolchains()?; - - // Convert the toolchain strings to Toolchain values - let toolchains = toolchains.into_iter(); - let toolchains = toolchains.map(|n| (n.clone(), self.get_toolchain(&n, true))); - - // Filter out toolchains that don't track a release channel - let toolchains = - toolchains.filter(|&(_, ref t)| t.as_ref().map(|t| t.is_tracking()).unwrap_or(false)); - - // Update toolchains and collect the results - let toolchains = toolchains.map(|(n, t)| { - let t = t.and_then(|t| { - let t = t.install_from_dist(force_update); - if let Err(ref e) = t { - (self.notify_handler)(Notification::NonFatalError(e)); - } - t - }); - - (n, t) - }); - - Ok(toolchains.collect()) - } - pub fn toolchain_for_dir(&self, path: &Path) -> Result<(Toolchain, Option)> { self.find_override_toolchain_or_default(path) .and_then(|r| r.ok_or(ErrorKind::NoDefaultToolchain.into())) @@ -386,7 +339,7 @@ impl Cfg { ) -> Result { let ref toolchain = self.get_toolchain(toolchain, false)?; if install_if_missing && !toolchain.exists() { - toolchain.install_from_dist(false)?; + toolchain.install_from_dist()?; } toolchain.create_command(binary) diff --git a/src/elan/errors.rs b/src/elan/errors.rs index d88a079..64b0f54 100644 --- a/src/elan/errors.rs +++ b/src/elan/errors.rs @@ -16,6 +16,10 @@ error_chain! { } errors { + InvalidToolchainName(t: String) { + description("invalid toolchain name") + display("invalid toolchain name: '{}'", t) + } UnknownMetadataVersion(v: String) { description("unknown metadata version") display("unknown metadata version: '{}'", v) diff --git a/src/elan/install.rs b/src/elan/install.rs index f94948f..5119f3e 100644 --- a/src/elan/install.rs +++ b/src/elan/install.rs @@ -27,7 +27,7 @@ pub fn check_self_update() -> Result> { // Get current version let current_version = env!("CARGO_PKG_VERSION"); - let tag = fetch_latest_release_tag("leanprover/elan")?; + let tag = fetch_latest_release_tag("leanprover/elan", None)?; let available_version = &tag[1..]; Ok(if available_version == current_version { None } else { Some(available_version.to_owned()) }) @@ -37,12 +37,9 @@ pub fn check_self_update() -> Result> { pub enum InstallMethod<'a> { Copy(&'a Path), Link(&'a Path), - // bool is whether to force an update Dist( &'a dist::ToolchainDesc, - Option<&'a Path>, DownloadCfg<'a>, - bool, ), } @@ -67,31 +64,19 @@ impl<'a> InstallMethod<'a> { utils::symlink_dir(src, &path, &|n| notify_handler(n.into()))?; Ok(true) } - InstallMethod::Dist(toolchain, update_hash, dl_cfg, force_update) => { + InstallMethod::Dist(toolchain, dl_cfg) => { if let Some(version) = check_self_update()? { notify_handler(Notification::NewVersionAvailable(version)); } let prefix = &InstallPrefix::from(path.to_owned()); - let maybe_new_hash = dist::update_from_dist( + dist::install_from_dist( dl_cfg, - update_hash, toolchain, prefix, - &[], - &[], - force_update, )?; - if let Some(hash) = maybe_new_hash { - if let Some(hash_file) = update_hash { - utils::write_file("update hash", hash_file, &hash)?; - } - - Ok(true) - } else { - Ok(false) - } + Ok(true) } } } diff --git a/src/elan/notifications.rs b/src/elan/notifications.rs index 2695e56..39b8c6f 100644 --- a/src/elan/notifications.rs +++ b/src/elan/notifications.rs @@ -14,7 +14,7 @@ pub enum Notification<'a> { Utils(elan_utils::Notification<'a>), Temp(temp::Notification<'a>), - SetDefaultToolchain(&'a ToolchainDesc), + SetDefaultToolchain(&'a str), SetOverrideToolchain(&'a Path, &'a ToolchainDesc), LookingForToolchain(&'a ToolchainDesc), ToolchainDirectory(&'a Path, &'a ToolchainDesc), diff --git a/src/elan/settings.rs b/src/elan/settings.rs index c6d696e..76202fd 100644 --- a/src/elan/settings.rs +++ b/src/elan/settings.rs @@ -73,7 +73,7 @@ pub enum TelemetryMode { #[derive(Clone, Debug, PartialEq)] pub struct Settings { pub version: String, - pub default_toolchain: Option, + pub default_toolchain: Option, pub overrides: BTreeMap, pub telemetry: TelemetryMode, } @@ -140,7 +140,7 @@ impl Settings { } Ok(Settings { version: version, - default_toolchain: get_opt_string(&mut table, "default_toolchain", path)?.map(|s| ToolchainDesc::from_str(&s)).map_or(Ok(None), |r| r.map(Some))?, + default_toolchain: get_opt_string(&mut table, "default_toolchain", path)?, overrides: Self::table_to_overrides(&mut table, path)?, telemetry: if get_opt_bool(&mut table, "telemetry", path)?.unwrap_or(false) { TelemetryMode::On @@ -155,7 +155,7 @@ impl Settings { result.insert("version".to_owned(), toml::Value::String(self.version)); if let Some(v) = self.default_toolchain { - result.insert("default_toolchain".to_owned(), toml::Value::String(v.to_string())); + result.insert("default_toolchain".to_owned(), toml::Value::String(v)); } let overrides = Self::overrides_to_table(self.overrides); @@ -176,7 +176,7 @@ impl Settings { for (k, v) in pkg_table { if let toml::Value::String(t) = v { - result.insert(k, ToolchainDesc::from_str(&t)?); + result.insert(k, ToolchainDesc::from_resolved_str(&t)?); } } diff --git a/src/elan/toolchain.rs b/src/elan/toolchain.rs index 6b1d058..19e638c 100644 --- a/src/elan/toolchain.rs +++ b/src/elan/toolchain.rs @@ -4,6 +4,7 @@ use elan_dist::dist::ToolchainDesc; use elan_dist::download::DownloadCfg; use elan_dist::manifest::Component; use elan_utils::utils; +use elan_utils::utils::fetch_url; use env_var; use errors::*; use install::{self, InstallMethod}; @@ -15,12 +16,14 @@ use std::ffi::OsStr; use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::process::Command; +use regex::Regex; + +const DEFAULT_ORIGIN: &str = "leanprover/lean4"; /// A fully resolved reference to a toolchain which may or may not exist pub struct Toolchain<'a> { cfg: &'a Cfg, pub desc: ToolchainDesc, - dir_name: String, path: PathBuf, dist_handler: Box, } @@ -39,21 +42,51 @@ pub enum UpdateStatus { Unchanged, } +pub fn lookup_toolchain_desc(cfg: &Cfg, name: &str) -> Result { + let pattern = r"^(?:([a-zA-Z0-9-]+[/][a-zA-Z0-9-]+)[:])?([a-zA-Z0-9-.]+)$"; + + let re = Regex::new(&pattern).unwrap(); + if let Some(c) = re.captures(name) { + let mut release = c.get(2).unwrap().as_str().to_owned(); + let local_tc = Toolchain::from(cfg, &ToolchainDesc::Local { name: release.clone() }); + if local_tc.exists() && local_tc.is_custom() { + return Ok(ToolchainDesc::Local { name: release }) + } + let mut origin = c.get(1).map(|s| s.as_str()).unwrap_or(DEFAULT_ORIGIN).to_owned(); + if release.starts_with("nightly") && !origin.ends_with("-nightly") { + origin = format!("{}-nightly", origin); + } + if release == "lean-toolchain" { + let toolchain_url = format!("https://raw.githubusercontent.com/{}/HEAD/lean-toolchain", origin); + return lookup_toolchain_desc(cfg, fetch_url(&toolchain_url)?.trim()) + } + if release == "stable" || release == "nightly" { + release = utils::fetch_latest_release_tag(&origin, +Some(&move |n| (cfg.notify_handler)(n.into())))?; + } + if release.starts_with(char::is_numeric) { + release = format!("v{}", release) + } + Ok(ToolchainDesc::Remote { origin, release }) + } else { + Err(ErrorKind::InvalidToolchainName(name.to_string()).into()) + } +} + impl<'a> Toolchain<'a> { - pub fn from(cfg: &'a Cfg, desc: &ToolchainDesc) -> Result { + pub fn from(cfg: &'a Cfg, desc: &ToolchainDesc) -> Self { //We need to replace ":" and "/" with "-" in the toolchain name in order to make a name which is a valid //name for a directory. let dir_name = desc.to_string().replace("/", "--").replace(":", "---"); let path = cfg.toolchains_dir.join(&dir_name[..]); - Ok(Toolchain { + Toolchain { cfg: cfg, desc: desc.clone(), - dir_name: dir_name, path: path.clone(), dist_handler: Box::new(move |n| (cfg.notify_handler)(n.into())), - }) + } } pub fn name(&self) -> String { self.desc.to_string() @@ -87,9 +120,6 @@ impl<'a> Toolchain<'a> { (self.cfg.notify_handler)(Notification::ToolchainNotInstalled(&self.desc)); return Ok(()); } - if let Some(update_hash) = self.update_hash()? { - utils::remove_file("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.desc)); @@ -99,7 +129,7 @@ impl<'a> Toolchain<'a> { fn install(&self, install_method: InstallMethod) -> Result { let exists = self.exists(); if exists { - (self.cfg.notify_handler)(Notification::UpdatingToolchain(&self.desc)); + return Err(format!("'{}' is already installed", self.desc).into()) } else { (self.cfg.notify_handler)(Notification::InstallingToolchain(&self.desc)); } @@ -130,13 +160,6 @@ impl<'a> Toolchain<'a> { Ok(UpdateStatus::Unchanged) } } - fn update_hash(&self) -> Result> { - if self.is_symlink() { - Ok(None) - } else { - Ok(Some(self.cfg.get_hash_file(&self.dir_name, true)?)) - } - } fn download_cfg(&self) -> DownloadCfg { DownloadCfg { @@ -145,28 +168,19 @@ impl<'a> Toolchain<'a> { } } - pub fn install_from_dist(&self, force_update: bool) -> Result { - let update_hash = self.update_hash()?; + pub fn install_from_dist(&self) -> Result { self.install(InstallMethod::Dist( &self.desc, - update_hash.as_ref().map(|p| &**p), self.download_cfg(), - force_update, )) } pub fn install_from_dist_if_not_installed(&self) -> Result { - 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_tracking(&self) -> bool { - self.desc.is_tracking() - } pub fn install_from_dir(&self, src: &Path, link: bool) -> Result<()> { let mut pathbuf = PathBuf::from(src); @@ -262,9 +276,6 @@ impl<'a> Toolchain<'a> { Ok(utils::open_browser(&self.doc_path(relative)?)?) } - pub fn make_default(&self) -> Result<()> { - self.cfg.set_default(&self.desc) - } pub fn make_override(&self, path: &Path) -> Result<()> { Ok(self.cfg.settings_file.with_mut(|s| { s.add_override(path, self.desc.clone(), self.cfg.notify_handler.as_ref());