diff --git a/Cargo.lock b/Cargo.lock index 2694e081c6..0932ee4cd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -859,6 +859,12 @@ dependencies = [ "syn", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "h2" version = "0.2.6" @@ -1815,6 +1821,7 @@ dependencies = [ "error-chain", "flate2", "git-testament", + "glob", "home", "lazy_static", "libc", diff --git a/Cargo.toml b/Cargo.toml index 1a453c1254..5cb4ace1cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ download = {path = "download"} error-chain = "0.12" flate2 = "1" git-testament = "0.1.4" +glob = "0.3" home = {git = "https://github.com/rbtcollins/home", rev = "a243ee2fbee6022c57d56f5aa79aefe194eabe53"} lazy_static = "1" libc = "0.2" diff --git a/src/cli/errors.rs b/src/cli/errors.rs index 7b5a6ba849..3f9643acc2 100644 --- a/src/cli/errors.rs +++ b/src/cli/errors.rs @@ -24,6 +24,7 @@ error_chain! { Temp(temp::Error); Io(io::Error); Term(term::Error); + Glob(glob::PatternError); } errors { diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 3e2b7bb33d..19fe26e59f 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -7,6 +7,7 @@ use std::process::Command; use std::str::FromStr; use clap::{App, AppSettings, Arg, ArgGroup, ArgMatches, Shell, SubCommand}; +use glob::Pattern; use super::common; use super::errors::*; @@ -1312,10 +1313,23 @@ fn toolchain_link(cfg: &Cfg, m: &ArgMatches<'_>) -> Result { } fn toolchain_remove(cfg: &mut Cfg, m: &ArgMatches<'_>) -> Result { - for toolchain in m.values_of("toolchain").unwrap() { - let toolchain = cfg.get_toolchain(toolchain, false)?; - toolchain.remove()?; + for pattern_str in m.values_of("toolchain").unwrap() { + let pattern = Pattern::new(&pattern_str)?; + + let mut toolchains = cfg.get_toolchains_from_glob(pattern)?.peekable(); + + if toolchains.peek().is_some() { + // This pattern matched some toolchains, so remove each of the ones it matched. + for toolchain in toolchains { + toolchain.remove()?; + } + } else { + // It didn't match any toolchains, so treat it as a partial toolchain specifier. + let toolchain = cfg.get_toolchain(pattern_str, false)?; + toolchain.remove()?; + } } + Ok(utils::ExitCode(0)) } diff --git a/src/config.rs b/src/config.rs index 2d26d7bdcd..5cd01d2d7a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,11 +1,13 @@ use std::borrow::Cow; use std::fmt::{self, Display}; use std::io; +use std::iter; use std::path::{Path, PathBuf}; use std::process::Command; use std::str::FromStr; use std::sync::Arc; +use glob::Pattern; use pgp::{Deserializable, SignedPublicKey}; use serde::Deserialize; @@ -362,6 +364,16 @@ impl Cfg { Toolchain::from(self, name) } + pub fn get_toolchains_from_glob( + &self, + pattern: Pattern, + ) -> Result>> { + Ok(self + .list_toolchains_iter()? + .filter(move |toolchain| pattern.matches(toolchain)) + .map(move |toolchain| Toolchain::from(self, &toolchain).unwrap())) + } + pub fn verify_toolchain(&self, name: &str) -> Result> { let toolchain = self.get_toolchain(name, false)?; toolchain.verify()?; @@ -694,18 +706,21 @@ impl Cfg { } pub fn list_toolchains(&self) -> Result> { - if utils::is_directory(&self.toolchains_dir) { - let mut toolchains: Vec<_> = utils::read_dir("toolchains", &self.toolchains_dir)? - .filter_map(io::Result::ok) - .filter(|e| e.file_type().map(|f| !f.is_file()).unwrap_or(false)) - .filter_map(|e| e.file_name().into_string().ok()) - .collect(); - - utils::toolchain_sort(&mut toolchains); + let mut toolchains: Vec<_> = self.list_toolchains_iter()?.collect(); + utils::toolchain_sort(&mut toolchains); + Ok(toolchains) + } - Ok(toolchains) + fn list_toolchains_iter(&self) -> Result>> { + if utils::is_directory(&self.toolchains_dir) { + Ok(Box::new( + utils::read_dir("toolchains", &self.toolchains_dir)? + .filter_map(io::Result::ok) + .filter(|e| e.file_type().map(|f| !f.is_file()).unwrap_or(false)) + .filter_map(|e| e.file_name().into_string().ok()), + )) } else { - Ok(Vec::new()) + Ok(Box::new(iter::empty())) } } diff --git a/tests/cli-rustup.rs b/tests/cli-rustup.rs index 782948a560..108a4c5423 100644 --- a/tests/cli-rustup.rs +++ b/tests/cli-rustup.rs @@ -1224,6 +1224,21 @@ fn toolchain_uninstall_is_like_uninstall() { }); } +#[test] +fn toolchain_uninstall_pattern() { + setup(&|config| { + expect_ok(config, &["rustup", "uninstall", "stable-*"]); + expect_ok(config, &["rustup", "uninstall", "nightly-*"]); + let mut cmd = clitools::cmd(config, "rustup", &["show"]); + clitools::env(config, &mut cmd); + let out = cmd.output().unwrap(); + assert!(out.status.success()); + let stdout = String::from_utf8(out.stdout).unwrap(); + assert!(!stdout.contains(for_host!("'stable-{}'"))); + assert!(!stdout.contains(for_host!("'nightly-2015-01-01-{}'"))); + }); +} + #[test] fn toolchain_update_is_like_update_except_that_bare_install_is_an_error() { setup(&|config| { diff --git a/tests/mock/clitools.rs b/tests/mock/clitools.rs index 820bad8c2e..ff25155e03 100644 --- a/tests/mock/clitools.rs +++ b/tests/mock/clitools.rs @@ -99,9 +99,9 @@ pub fn setup(s: Scenario, f: &dyn Fn(&mut Config)) { env::remove_var("RUSTUP_TOOLCHAIN"); env::remove_var("SHELL"); env::remove_var("ZDOTDIR"); - // clap does it's own terminal colour probing, and that isn't + // clap does its own terminal colour probing, and that isn't // trait-controllable, but it does honour the terminal. To avoid testing - // claps code, lie about whatever terminal this process was started under. + // clap's code, lie about whatever terminal this process was started under. env::set_var("TERM", "dumb"); match env::var("RUSTUP_BACKTRACE") {