Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/cli/rustup_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -540,8 +540,13 @@ enum SelfSubcmd {

/// Uninstall rustup
Uninstall {
/// 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
Expand Down Expand Up @@ -724,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 {
Expand Down
33 changes: 27 additions & 6 deletions src/cli/self_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<utils::ExitCode> {
pub(crate) fn uninstall(
no_prompt: bool,
no_modify_path: bool,
process: &Process,
) -> Result<utils::ExitCode> {
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");
Expand All @@ -983,10 +998,14 @@ pub(crate) fn uninstall(no_prompt: bool, process: &Process) -> Result<utils::Exi

if !no_prompt {
writeln!(process.stdout().lock())?;
let msg = format!(
pre_uninstall_msg!(),
cargo_home = canonical_cargo_home(process)?
);
let msg = if no_modify_path {
pre_uninstall_msg_no_modify_path!().to_owned()
} else {
format!(
pre_uninstall_msg!(),
cargo_home = canonical_cargo_home(process)?
)
};
md(&mut process.stdout(), msg);
if !common::confirm("\nContinue? (y/N)", false, process)? {
info!("aborting uninstallation");
Expand All @@ -1005,7 +1024,9 @@ pub(crate) fn uninstall(no_prompt: bool, process: &Process) -> Result<utils::Exi
info!("removing cargo home");

// Remove CARGO_HOME/bin from PATH
do_remove_from_path(process)?;
if !no_modify_path {
do_remove_from_path(process)?;
}

// Delete everything in CARGO_HOME *except* the rustup bin

Expand Down
69 changes: 69 additions & 0 deletions tests/suite/cli_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -266,6 +268,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;
Expand Down Expand Up @@ -422,6 +467,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() {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.