diff --git a/src/rustup-cli/rustup_mode.rs b/src/rustup-cli/rustup_mode.rs index 3b67d5a19f..2a77afa6b2 100644 --- a/src/rustup-cli/rustup_mode.rs +++ b/src/rustup-cli/rustup_mode.rs @@ -131,12 +131,14 @@ pub fn cli() -> App<'static, 'static> { .after_help(TOOLCHAIN_INSTALL_HELP) .setting(AppSettings::Hidden) // synonym for 'toolchain install' .arg(Arg::with_name("toolchain") - .required(true))) + .required(true) + .multiple(true))) .subcommand(SubCommand::with_name("update") .about("Update Rust toolchains") .after_help(UPDATE_HELP) .arg(Arg::with_name("toolchain") - .required(false)) + .required(false) + .multiple(true)) .arg(Arg::with_name("no-self-update") .help("Don't perform self update when running the `rustup` command") .long("no-self-update") @@ -199,28 +201,32 @@ pub fn cli() -> App<'static, 'static> { .subcommand(SubCommand::with_name("add") .about("Add a target to a Rust toolchain") .arg(Arg::with_name("target") - .required(true)) + .required(true) + .multiple(true)) .arg(Arg::with_name("toolchain") .long("toolchain") .takes_value(true))) .subcommand(SubCommand::with_name("remove") .about("Remove a target from a Rust toolchain") .arg(Arg::with_name("target") - .required(true)) + .required(true) + .multiple(true)) .arg(Arg::with_name("toolchain") .long("toolchain") .takes_value(true))) .subcommand(SubCommand::with_name("install") .setting(AppSettings::Hidden) // synonym for 'add' .arg(Arg::with_name("target") - .required(true)) + .required(true) + .multiple(true)) .arg(Arg::with_name("toolchain") .long("toolchain") .takes_value(true))) .subcommand(SubCommand::with_name("uninstall") .setting(AppSettings::Hidden) // synonym for 'remove' .arg(Arg::with_name("target") - .required(true)) + .required(true) + .multiple(true)) .arg(Arg::with_name("toolchain") .long("toolchain") .takes_value(true)))) @@ -237,7 +243,8 @@ pub fn cli() -> App<'static, 'static> { .subcommand(SubCommand::with_name("add") .about("Add a component to a Rust toolchain") .arg(Arg::with_name("component") - .required(true)) + .required(true) + .multiple(true)) .arg(Arg::with_name("toolchain") .long("toolchain") .takes_value(true)) @@ -247,7 +254,8 @@ pub fn cli() -> App<'static, 'static> { .subcommand(SubCommand::with_name("remove") .about("Remove a component from a Rust toolchain") .arg(Arg::with_name("component") - .required(true)) + .required(true) + .multiple(true)) .arg(Arg::with_name("toolchain") .long("toolchain") .takes_value(true)) @@ -613,24 +621,32 @@ fn target_list(cfg: &Cfg, m: &ArgMatches) -> Result<()> { fn target_add(cfg: &Cfg, m: &ArgMatches) -> Result<()> { let toolchain = try!(explicit_or_dir_toolchain(cfg, m)); - let target = m.value_of("target").expect(""); - let new_component = Component { - pkg: "rust-std".to_string(), - target: Some(TargetTriple::from_str(target)), - }; - Ok(try!(toolchain.add_component(new_component))) + for target in m.values_of("target").expect("") { + let new_component = Component { + pkg: "rust-std".to_string(), + target: Some(TargetTriple::from_str(target)), + }; + + try!(toolchain.add_component(new_component)); + } + + Ok(()) } fn target_remove(cfg: &Cfg, m: &ArgMatches) -> Result<()> { let toolchain = try!(explicit_or_dir_toolchain(cfg, m)); - let target = m.value_of("target").expect(""); - let new_component = Component { - pkg: "rust-std".to_string(), - target: Some(TargetTriple::from_str(target)), - }; - Ok(try!(toolchain.remove_component(new_component))) + for target in m.values_of("target").expect("") { + let new_component = Component { + pkg: "rust-std".to_string(), + target: Some(TargetTriple::from_str(target)), + }; + + try!(toolchain.remove_component(new_component)); + } + + Ok(()) } fn component_list(cfg: &Cfg, m: &ArgMatches) -> Result<()> { @@ -641,32 +657,38 @@ fn component_list(cfg: &Cfg, m: &ArgMatches) -> Result<()> { fn component_add(cfg: &Cfg, m: &ArgMatches) -> Result<()> { let toolchain = try!(explicit_or_dir_toolchain(cfg, m)); - let component = m.value_of("component").expect(""); let target = m.value_of("target").map(TargetTriple::from_str).or_else(|| { toolchain.desc().as_ref().ok().map(|desc| desc.target.clone()) }); - let new_component = Component { - pkg: component.to_string(), - target: target, - }; + for component in m.values_of("component").expect("") { + let new_component = Component { + pkg: component.to_string(), + target: target.clone(), + }; + + try!(toolchain.add_component(new_component)); + } - Ok(try!(toolchain.add_component(new_component))) + Ok(()) } fn component_remove(cfg: &Cfg, m: &ArgMatches) -> Result<()> { let toolchain = try!(explicit_or_dir_toolchain(cfg, m)); - let component = m.value_of("component").expect(""); let target = m.value_of("target").map(TargetTriple::from_str).or_else(|| { toolchain.desc().as_ref().ok().map(|desc| desc.target.clone()) }); - let new_component = Component { - pkg: component.to_string(), - target: target, - }; + for component in m.values_of("component").expect("") { + let new_component = Component { + pkg: component.to_string(), + target: target.clone(), + }; - Ok(try!(toolchain.remove_component(new_component))) + try!(toolchain.remove_component(new_component)); + } + + Ok(()) } fn explicit_or_dir_toolchain<'a>(cfg: &'a Cfg, m: &ArgMatches) -> Result> { diff --git a/src/rustup-mock/src/clitools.rs b/src/rustup-mock/src/clitools.rs index 6e6b5d5324..064fdfa792 100644 --- a/src/rustup-mock/src/clitools.rs +++ b/src/rustup-mock/src/clitools.rs @@ -370,6 +370,7 @@ fn build_mock_channel(s: Scenario, channel: &str, date: &str, let cross_std1 = build_mock_cross_std_installer(CROSS_ARCH1, date); let cross_std2 = build_mock_cross_std_installer(CROSS_ARCH2, date); let rust_src = build_mock_rust_src_installer(); + let rust_analysis = build_mock_rust_analysis_installer(host_triple); // Convert the mock installers to mock package definitions for the // mock dist server @@ -380,6 +381,7 @@ fn build_mock_channel(s: Scenario, channel: &str, date: &str, ("cargo", vec![(cargo, host_triple.clone())]), ("rust-docs", vec![(rust_docs, host_triple.clone())]), ("rust-src", vec![(rust_src, "*".to_string())]), + ("rust-analysis", vec![(rust_analysis, "*".to_string())]), ("rust", vec![(rust, host_triple.clone())])]; if s == Scenario::MultiHost { @@ -457,6 +459,10 @@ fn build_mock_channel(s: Scenario, channel: &str, date: &str, name: "rust-src".to_string(), target: "*".to_string(), }); + target_pkg.extensions.push(MockComponent { + name: "rust-analysis".to_string(), + target: target.to_string(), + }); } } @@ -554,6 +560,16 @@ fn build_mock_rust_doc_installer() -> MockInstallerBuilder { } } +fn build_mock_rust_analysis_installer(trip: &str) -> MockInstallerBuilder { + MockInstallerBuilder { + components: vec![ + (format!("rust-analysis-{}", trip), + vec![MockCommand::File(format!("lib/rustlib/{}/analysis/libfoo.json", trip))], + vec![(format!("lib/rustlib/{}/analysis/libfoo.json", trip), "".into())]) + ] + } +} + fn build_mock_rust_src_installer() -> MockInstallerBuilder { MockInstallerBuilder { components: vec![ diff --git a/tests/cli-rustup.rs b/tests/cli-rustup.rs index 00c26e4fbb..68e22f07c2 100644 --- a/tests/cli-rustup.rs +++ b/tests/cli-rustup.rs @@ -209,6 +209,32 @@ fn remove_target() { }); } +#[test] +fn add_remove_multiple_targets() { + setup(&|config| { + expect_ok(config, &["rustup", "default", "nightly"]); + expect_ok(config, &["rustup", "target", "add", + clitools::CROSS_ARCH1, + clitools::CROSS_ARCH2]); + let path = format!("toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", + &this_host_triple(), clitools::CROSS_ARCH1); + assert!(config.rustupdir.join(path).exists()); + let path = format!("toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", + &this_host_triple(), clitools::CROSS_ARCH2); + assert!(config.rustupdir.join(path).exists()); + + expect_ok(config, &["rustup", "target", "remove", + clitools::CROSS_ARCH1, + clitools::CROSS_ARCH2]); + let path = format!("toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", + &this_host_triple(), clitools::CROSS_ARCH1); + assert!(!config.rustupdir.join(path).exists()); + let path = format!("toolchains/nightly-{}/lib/rustlib/{}/lib/libstd.rlib", + &this_host_triple(), clitools::CROSS_ARCH2); + assert!(!config.rustupdir.join(path).exists()); + }); +} + #[test] fn list_targets() { setup(&|config| { @@ -586,6 +612,30 @@ fn remove_component() { }); } +#[test] +fn add_remove_multiple_components() { + let files = ["lib/rustlib/src/rust-src/foo.rs".to_owned(), + format!("lib/rustlib/{}/analysis/libfoo.json", this_host_triple())]; + + setup(&|config| { + expect_ok(config, &["rustup", "default", "nightly"]); + expect_ok(config, &["rustup", "component", "add", "rust-src", "rust-analysis"]); + for file in &files { + let path = format!("toolchains/nightly-{}/{}", + this_host_triple(), file); + let path = config.rustupdir.join(path); + assert!(path.exists()); + } + expect_ok(config, &["rustup", "component", "remove", "rust-src", "rust-analysis"]); + for file in &files { + let path = format!("toolchains/nightly-{}/{}", + this_host_triple(), file); + let path = config.rustupdir.join(path); + assert!(!path.parent().unwrap().exists()); + } + }); +} + // Run without setting RUSTUP_HOME, with setting HOME and USERPROFILE fn run_no_home(config: &Config, args: &[&str], env: &[(&str, &str)]) -> process::Output { let home_dir_str = &format!("{}", config.homedir.display());