From d7919dd3419bc05813798232698031f6c3313831 Mon Sep 17 00:00:00 2001 From: cyqsimon <28627918+cyqsimon@users.noreply.github.com> Date: Wed, 15 Oct 2025 17:25:57 +0800 Subject: [PATCH 1/4] cli: add help text for `rustup self uninstall -y` Help text is mirrored from `rustup-init -y` --- src/cli/rustup_mode.rs | 1 + .../rustup_self_cmd_uninstall_cmd_help_flag.stdout.term.svg | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index a3137c6f51..2619ad4df2 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -540,6 +540,7 @@ enum SelfSubcmd { /// Uninstall rustup Uninstall { + /// Disable confirmation prompt #[arg(short = 'y')] no_prompt: bool, }, diff --git a/tests/suite/cli_rustup_ui/rustup_self_cmd_uninstall_cmd_help_flag.stdout.term.svg b/tests/suite/cli_rustup_ui/rustup_self_cmd_uninstall_cmd_help_flag.stdout.term.svg index e164ee4fee..4c8fbc58ef 100644 --- a/tests/suite/cli_rustup_ui/rustup_self_cmd_uninstall_cmd_help_flag.stdout.term.svg +++ b/tests/suite/cli_rustup_ui/rustup_self_cmd_uninstall_cmd_help_flag.stdout.term.svg @@ -30,7 +30,7 @@ Options: - -y + -y Disable confirmation prompt -h, --help Print help From e462360df526edbb98c6dde744ec9f0ca6590998 Mon Sep 17 00:00:00 2001 From: cyqsimon <28627918+cyqsimon@users.noreply.github.com> Date: Wed, 15 Oct 2025 11:48:38 +0800 Subject: [PATCH 2/4] cli: add `rustup self uninstall --no-modify-path` --- src/cli/rustup_mode.rs | 9 ++++- src/cli/self_update.rs | 33 +++++++++++++++---- ...md_uninstall_cmd_help_flag.stdout.term.svg | 10 +++--- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 2619ad4df2..34f267dbc6 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -543,6 +543,10 @@ enum SelfSubcmd { /// Disable confirmation prompt #[arg(short = 'y')] no_prompt: bool, + + /// Do not clean up the `PATH` environment variable + #[arg(long)] + no_modify_path: bool, }, /// Upgrade the internal data format @@ -725,7 +729,10 @@ pub async fn main( RustupSubcmd::Man { command, toolchain } => man(cfg, &command, toolchain).await, RustupSubcmd::Self_ { subcmd } => match subcmd { SelfSubcmd::Update => self_update::update(cfg).await, - SelfSubcmd::Uninstall { no_prompt } => self_update::uninstall(no_prompt, process), + SelfSubcmd::Uninstall { + no_prompt, + no_modify_path, + } => self_update::uninstall(no_prompt, no_modify_path, process), SelfSubcmd::UpgradeData => cfg.upgrade_data().map(|_| ExitCode(0)), }, RustupSubcmd::Set { subcmd } => match subcmd { diff --git a/src/cli/self_update.rs b/src/cli/self_update.rs index 5e1e7b78aa..ee09c72537 100644 --- a/src/cli/self_update.rs +++ b/src/cli/self_update.rs @@ -474,6 +474,17 @@ This will uninstall all Rust toolchains and data, and remove }; } +macro_rules! pre_uninstall_msg_no_modify_path { + () => { + r"# Thanks for hacking in Rust! + +This will uninstall all Rust toolchains and data. +Your `PATH` environment variable will not be touched. + +" + }; +} + static DEFAULT_UPDATE_ROOT: &str = "https://static.rust-lang.org/rustup"; fn update_root(process: &Process) -> String { @@ -968,7 +979,11 @@ async fn maybe_install_rust(opts: InstallOpts<'_>, cfg: &mut Cfg<'_>) -> Result< Ok(()) } -pub(crate) fn uninstall(no_prompt: bool, process: &Process) -> Result { +pub(crate) fn uninstall( + no_prompt: bool, + no_modify_path: bool, + process: &Process, +) -> Result { if cfg!(feature = "no-self-update") { error!("self-uninstall is disabled for this build of rustup"); error!("you should probably use your system package manager to uninstall rustup"); @@ -983,10 +998,14 @@ pub(crate) fn uninstall(no_prompt: bool, process: &Process) -> Result Result + Options: - -y Disable confirmation prompt + -y Disable confirmation prompt - -h, --help Print help + --no-modify-path Do not clean up the `PATH` environment variable - + -h, --help Print help + + From 1d20c58be854fa113701b87424b53767faef91c0 Mon Sep 17 00:00:00 2001 From: cyqsimon <28627918+cyqsimon@users.noreply.github.com> Date: Mon, 20 Oct 2025 13:51:47 +0800 Subject: [PATCH 3/4] cli: add tests for `rustup self uninstall --no-modify-path` --- tests/suite/cli_paths.rs | 67 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/suite/cli_paths.rs b/tests/suite/cli_paths.rs index 3059d82b53..6e6d8c29a1 100644 --- a/tests/suite/cli_paths.rs +++ b/tests/suite/cli_paths.rs @@ -266,6 +266,49 @@ error: could not amend shell profile[..] } } + #[tokio::test] + async fn uninstall_doesnt_modify_rcs_with_no_modify_path() { + let cx = CliTestContext::new(Scenario::Empty).await; + let rcs = [ + ".bashrc", + ".bash_profile", + ".bash_login", + ".profile", + // This requires zsh to be installed, so we test it on macOS only. + #[cfg(target_os = "macos")] + ".zshenv", + ] + .map(|rc| cx.config.homedir.join(rc)); + + for rc in &rcs { + raw::write_file(rc, FAKE_RC).unwrap(); + } + + let expected = FAKE_RC.to_owned() + &source(cx.config.cargodir.display(), POSIX_SH); + + cx.config.expect(&INIT_NONE).await.is_ok(); + // sanity check + for rc in &rcs { + let new_rc = fs::read_to_string(rc).unwrap(); + assert_eq!( + new_rc, expected, + "Rc file {rc:?} does not contain a source after rustup-init", + ); + } + + cx.config + .expect(&["rustup", "self", "uninstall", "-y", "--no-modify-path"]) + .await + .is_ok(); + for rc in &rcs { + let new_rc = fs::read_to_string(rc).unwrap(); + assert_eq!( + new_rc, expected, + "Rc file {rc:?} does not contain a source after uninstall", + ); + } + } + #[tokio::test] async fn install_adds_sources_while_removing_legacy_paths() { let cx = CliTestContext::new(Scenario::Empty).await; @@ -422,6 +465,30 @@ mod windows { assert!(!get_path_().contains(&cfg_path)); } + #[tokio::test] + async fn uninstall_doesnt_affect_path_with_no_modify_path() { + let cx = CliTestContext::new(Scenario::Empty).await; + let _guard = RegistryGuard::new(&USER_PATH).unwrap(); + let cfg_path = cx.config.cargodir.join("bin").display().to_string(); + let get_path_ = || { + HSTRING::try_from(get_path().unwrap().unwrap()) + .unwrap() + .to_string() + }; + + cx.config.expect(&INIT_NONE).await.is_ok(); + cx.config + .expect(&["rustup", "self", "uninstall", "-y", "--no-modify-path"]) + .await + .is_ok(); + assert!( + get_path_().contains(cfg_path.trim_matches('"')), + "`{}` not in `{}`", + cfg_path, + get_path_() + ); + } + #[tokio::test] /// Smoke test for end-to-end code connectivity of the installer path mgmt on windows. async fn install_uninstall_affect_path_with_non_unicode() { From 4ab8ebfd69413146a77f2ccc16534eb3726a13c4 Mon Sep 17 00:00:00 2001 From: cyqsimon <28627918+cyqsimon@users.noreply.github.com> Date: Wed, 22 Oct 2025 18:07:51 +0800 Subject: [PATCH 4/4] cli: update `uninstall_removes_source_from_rcs` to mirror `uninstall_doesnt_modify_rcs_with_no_modify_path` --- tests/suite/cli_paths.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/suite/cli_paths.rs b/tests/suite/cli_paths.rs index 6e6d8c29a1..f102dd5234 100644 --- a/tests/suite/cli_paths.rs +++ b/tests/suite/cli_paths.rs @@ -244,6 +244,8 @@ error: could not amend shell profile[..] ".bash_profile", ".bash_login", ".profile", + // This requires zsh to be installed, so we test it on macOS only. + #[cfg(target_os = "macos")] ".zshenv", ] .iter()