Skip to content
Open
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
45 changes: 15 additions & 30 deletions src/cli/self_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,36 +445,20 @@ but will not be added automatically."
};
}

#[cfg(not(windows))]
macro_rules! post_install_msg_unix_source_env {
Copy link
Copy Markdown
Contributor

@djc djc Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View changes since the review

I think this is a good idea, but I wonder if we should also keep some kind of indication that there is support for these other shells.

Maybe there should be a page in the documentation that covers this (if there isn't one already), and we could link to it here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about this after your comment. I looked at the code and there are not many places we put "just in case, look at the docs here: [url]".

I think we already rely very heavily on the "get_supported_shells" function and it seems to have served well over the years. If they have a shell that isn't discovered by "get_supported_shells" for some reason, it's .rc files or whatever it uses will not get updated by rustup in the first place, and we don't show a documentation message related to that issue. Like there's no "just in case we didn't find your shell, look here: [url]" message in what we're already doing.

So I think it's a better strategy to just make sure the "get_supported_shells" does a really good job of detecting whatever shells you have, which I think we have done since there I searched issues for "is:issue state:open get_supported_shells" and "shells missing" and there don't seem to be any complaints about it.

Copy link
Copy Markdown
Member

@rami3l rami3l Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@djc @ryankopf Actually a smarter way to do this would be to dump all env files in one place and tell the user "just source anything you want in this folder if you install any new shells later".

This is part of the plan (#3639), but for now I'd suggest postponing this because we are making the installation path change (#247) and the former change will be easier afterwards.

Your proposal is going exactly in the direction we originally wanted though, thanks again!

() => {
r#"To configure your current shell, you need to source
the corresponding `env` file under {cargo_home}.

This is usually done by running one of the following (note the leading DOT):
. "{cargo_home}/env" # For sh/bash/zsh/ash/dash/pdksh
source "{cargo_home}/env.fish" # For fish
source "{cargo_home_nushell}/env.nu" # For nushell
source "{cargo_home}/env.tcsh" # For tcsh
. "{cargo_home}/env.ps1" # For pwsh
source "{cargo_home}/env.xsh" # For xonsh
"#
};
}

#[cfg(not(windows))]
macro_rules! post_install_msg_unix {
() => {
concat!(
r"# Rust is installed now. Great!
r"# Rust is installed now. Great!

To get started you may need to restart your current shell.
This would reload your `PATH` environment variable to include
Cargo's bin directory ({cargo_home}/bin).

",
post_install_msg_unix_source_env!(),
)
To configure your current shell, you need to source
the corresponding `env` file under {cargo_home}.

This is usually done by running one of the following (note the leading DOT):
{source_env_lines}"
};
}

Expand All @@ -494,15 +478,16 @@ Cargo's bin directory ({cargo_home}\\bin).
#[cfg(not(windows))]
macro_rules! post_install_msg_unix_no_modify_path {
() => {
concat!(
r"# Rust is installed now. Great!
r"# Rust is installed now. Great!

To get started you need Cargo's bin directory ({cargo_home}/bin) in your `PATH`
environment variable. This has not been done automatically.

",
post_install_msg_unix_source_env!(),
)
To configure your current shell, you need to source
the corresponding `env` file under {cargo_home}.

This is usually done by running one of the following (note the leading DOT):
{source_env_lines}"
};
}

Expand Down Expand Up @@ -661,19 +646,19 @@ pub(crate) async fn install(
format!(post_install_msg_win!(), cargo_home = cargo_home)
};
#[cfg(not(windows))]
let cargo_home_nushell = Nu.cargo_home_str(cfg.process)?;
let source_env_lines = shell::build_source_env_lines(cfg.process);
#[cfg(not(windows))]
let msg = if no_modify_path {
format!(
post_install_msg_unix_no_modify_path!(),
cargo_home = cargo_home,
cargo_home_nushell = cargo_home_nushell,
source_env_lines = source_env_lines,
)
} else {
format!(
post_install_msg_unix!(),
cargo_home = cargo_home,
cargo_home_nushell = cargo_home_nushell,
source_env_lines = source_env_lines,
)
};
md(&mut term, msg);
Expand Down
59 changes: 59 additions & 0 deletions src/cli/self_update/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,30 @@ fn enumerate_shells() -> Vec<Shell> {
]
}

/// Builds the shell source lines for the post-install message, showing only
/// shells that are available on the current system. Shells sharing the same
/// env file are grouped onto one line (e.g. sh/bash/zsh all use `env`).
pub(crate) fn build_source_env_lines(process: &Process) -> String {
let mut groups = Vec::<(String, Vec<&'static str>)>::new();
for shell in get_available_shells(process) {
let Ok(src) = shell.source_string(process) else {
continue;
};
if let Some(names) = groups
.iter_mut()
.find_map(|(s, names)| (*s == src).then_some(names))
{
names.push(shell.name());
} else {
groups.push((src, vec![shell.name()]));
}
}
groups
.into_iter()
.map(|(src, names)| format!(" {} # For {}\n", src, names.join("/")))
.collect()
}

pub(crate) fn get_available_shells(process: &Process) -> impl Iterator<Item = Shell> + '_ {
enumerate_shells()
.into_iter()
Expand All @@ -84,6 +108,9 @@ pub(crate) trait UnixShell {
// heuristic should be used, assuming shells exist if any traces do.
fn does_exist(&self, process: &Process) -> bool;

// Returns the display name of the shell, used in post-install messages.
fn name(&self) -> &'static str;

// Gives all rcfiles of a given shell that Rustup is concerned with.
// Used primarily in checking rcfiles for cleanup.
fn rcfiles(&self, process: &Process) -> Vec<PathBuf>;
Expand Down Expand Up @@ -128,6 +155,10 @@ impl UnixShell for Posix {
true
}

fn name(&self) -> &'static str {
"sh/ash/dash/pdksh"
}

fn rcfiles(&self, process: &Process) -> Vec<PathBuf> {
match process.home_dir() {
Some(dir) => vec![dir.join(".profile")],
Expand All @@ -149,6 +180,10 @@ impl UnixShell for Bash {
!self.update_rcs(process).is_empty()
}

fn name(&self) -> &'static str {
"bash"
}

fn rcfiles(&self, process: &Process) -> Vec<PathBuf> {
// Bash also may read .profile, however Rustup already includes handling
// .profile as part of POSIX and always does setup for POSIX shells.
Expand Down Expand Up @@ -197,6 +232,10 @@ impl UnixShell for Zsh {
|| utils::find_cmd(&["zsh"], process).is_some()
}

fn name(&self) -> &'static str {
"zsh"
}

fn rcfiles(&self, process: &Process) -> Vec<PathBuf> {
[Zsh::zdotdir(process).ok(), process.home_dir()]
.iter()
Expand Down Expand Up @@ -229,6 +268,10 @@ impl UnixShell for Fish {
|| utils::find_cmd(&["fish"], process).is_some()
}

fn name(&self) -> &'static str {
"fish"
}

// > "$XDG_CONFIG_HOME/fish/conf.d" (or "~/.config/fish/conf.d" if that variable is unset) for the user
// from <https://github.com/fish-shell/fish-shell/issues/3170#issuecomment-228311857>
fn rcfiles(&self, process: &Process) -> Vec<PathBuf> {
Expand Down Expand Up @@ -278,6 +321,10 @@ impl UnixShell for Nu {
|| utils::find_cmd(&["nu"], process).is_some()
}

fn name(&self) -> &'static str {
"nushell"
}

fn rcfiles(&self, process: &Process) -> Vec<PathBuf> {
let mut paths = vec![];

Expand Down Expand Up @@ -329,6 +376,10 @@ impl UnixShell for Tcsh {
|| utils::find_cmd(&["tcsh"], process).is_some()
}

fn name(&self) -> &'static str {
"tcsh"
}

fn rcfiles(&self, process: &Process) -> Vec<PathBuf> {
let mut paths = vec![];

Expand Down Expand Up @@ -378,6 +429,10 @@ impl UnixShell for Pwsh {
|| utils::find_cmd(&["pwsh"], process).is_some()
}

fn name(&self) -> &'static str {
"pwsh"
}

fn rcfiles(&self, process: &Process) -> Vec<PathBuf> {
let mut paths = vec![];

Expand Down Expand Up @@ -453,6 +508,10 @@ impl UnixShell for Xonsh {
process.var("XONSHRC").is_ok() || utils::find_cmd(&["xonsh"], process).is_some()
}

fn name(&self) -> &'static str {
"xonsh"
}

fn rcfiles(&self, process: &Process) -> Vec<PathBuf> {
let mut paths = vec![];

Expand Down
Loading