From 5fe43ed3f26713c8a515c1e11fdaaf6efeed191e Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Thu, 5 Jun 2025 15:43:34 +0100 Subject: [PATCH] `brew (bundle|) sh`: use user's configuration but override prompts. This was more painful that I expected but will allow `brew bundle sh` and `brew sh` to use the user's configuration but use our custom prompt for Bash and ZSH. --- Library/Homebrew/bundle/commands/exec.rb | 12 +++++++ Library/Homebrew/cmd/bundle.rb | 12 +------ Library/Homebrew/test/utils/shell_spec.rb | 32 ++++++++++++++----- .../utils/bash/brew-sh-prompt-bashrc.bash | 12 +++++++ Library/Homebrew/utils/shell.rb | 22 ++++++++++--- .../utils/zsh/brew-sh-prompt-zshrc.zsh | 13 ++++++++ 6 files changed, 80 insertions(+), 23 deletions(-) create mode 100644 Library/Homebrew/utils/bash/brew-sh-prompt-bashrc.bash create mode 100644 Library/Homebrew/utils/zsh/brew-sh-prompt-zshrc.zsh diff --git a/Library/Homebrew/bundle/commands/exec.rb b/Library/Homebrew/bundle/commands/exec.rb index 103706dd76..d7babf8afc 100644 --- a/Library/Homebrew/bundle/commands/exec.rb +++ b/Library/Homebrew/bundle/commands/exec.rb @@ -170,6 +170,18 @@ module Homebrew end end return + elsif subcommand == "sh" + preferred_path = Utils::Shell.preferred_path(default: "/bin/bash") + notice = unless Homebrew::EnvConfig.no_env_hints? + <<~EOS + Your shell has been configured to use a build environment from your `Brewfile`. + This should help you build stuff. + Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`). + When done, type `exit`. + EOS + end + ENV["HOMEBREW_FORCE_API_AUTO_UPDATE"] = nil + args = [Utils::Shell.shell_with_prompt("brew bundle", preferred_path:, notice:)] end if services diff --git a/Library/Homebrew/cmd/bundle.rb b/Library/Homebrew/cmd/bundle.rb index 122341a741..b942802e5f 100755 --- a/Library/Homebrew/cmd/bundle.rb +++ b/Library/Homebrew/cmd/bundle.rb @@ -276,17 +276,7 @@ module Homebrew _subcommand, *named_args = args.named named_args when "sh" - preferred_path = Utils::Shell.preferred_path(default: "/bin/bash") - notice = unless Homebrew::EnvConfig.no_env_hints? - <<~EOS - Your shell has been configured to use a build environment from your `Brewfile`. - This should help you build stuff. - Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`). - When done, type `exit`. - EOS - end - ENV["HOMEBREW_FORCE_API_AUTO_UPDATE"] = nil - [Utils::Shell.shell_with_prompt("brew bundle", preferred_path:, notice:)] + ["sh"] when "env" ["env"] end diff --git a/Library/Homebrew/test/utils/shell_spec.rb b/Library/Homebrew/test/utils/shell_spec.rb index 66cf8edfd8..bd07c39d71 100644 --- a/Library/Homebrew/test/utils/shell_spec.rb +++ b/Library/Homebrew/test/utils/shell_spec.rb @@ -107,18 +107,34 @@ RSpec.describe Utils::Shell do end describe "::shell_with_prompt" do + let(:home) { HOMEBREW_TEMP } + let(:notice) { "" } + let(:prompt) { "test" } + let(:path) { "/some/path" } + it "returns zsh-specific prompt configuration" do - ENV["SHELL"] = "/bin/zsh" - expect(described_class.shell_with_prompt("test", preferred_path: "/bin/zsh", notice: "")).to eq( - "PROMPT='%B%F{green}test%f %F{blue}$%f%b ' RPROMPT='[%B%F{red}%~%f%b]' /bin/zsh -f", - ) + preferred_path = "/bin/zsh" + ENV["SHELL"] = preferred_path + ENV["PATH"] = path + zdotdir = "#{HOMEBREW_TEMP}/brew-zsh-prompt-#{Process.euid}" + expect(described_class.shell_with_prompt(prompt, preferred_path:, notice:, home:)).to eq \ + "BREW_PROMPT_PATH=\"#{path}\" BREW_PROMPT_TYPE=\"#{prompt}\" ZDOTDIR=\"#{zdotdir}\" #{preferred_path}" + end + + it "returns bash-specific prompt configuration" do + preferred_path = "/bin/bash" + ENV["SHELL"] = "/bin/bash" + ENV["PATH"] = path + rcfile = "#{HOMEBREW_LIBRARY_PATH}/utils/bash/brew-sh-prompt-bashrc.bash" + expect(described_class.shell_with_prompt(prompt, preferred_path:, notice:, home:)).to eq \ + "BREW_PROMPT_PATH=\"#{path}\" BREW_PROMPT_TYPE=\"#{prompt}\" #{preferred_path} --rcfile \"#{rcfile}\"" end it "returns generic shell prompt configuration" do - ENV["SHELL"] = "/bin/bash" - expect(described_class.shell_with_prompt("test", preferred_path: "/bin/bash", notice: "")).to eq( - "PS1=\"\\[\\033[1;32m\\]brew \\[\\033[1;31m\\]\\w \\[\\033[1;34m\\]$\\[\\033[0m\\] \" /bin/bash", - ) + preferred_path = "/bin/dash" + ENV["SHELL"] = preferred_path + expect(described_class.shell_with_prompt(prompt, preferred_path:, notice:, home:)).to eq \ + "PS1=\"\\[\\033[1;32m\\]#{prompt} \\[\\033[1;31m\\]\\w \\[\\033[1;34m\\]$\\[\\033[0m\\] \" #{preferred_path}" end it "outputs notice when provided" do diff --git a/Library/Homebrew/utils/bash/brew-sh-prompt-bashrc.bash b/Library/Homebrew/utils/bash/brew-sh-prompt-bashrc.bash new file mode 100644 index 0000000000..d4d9dd7e3e --- /dev/null +++ b/Library/Homebrew/utils/bash/brew-sh-prompt-bashrc.bash @@ -0,0 +1,12 @@ +# Read the user's ~/.bashrc first +if [[ -f "${HOME}/.bashrc" ]] +then + source "${HOME}/.bashrc" +fi + +# Override the user's Bash prompt with our custom prompt +export PS1="\\[\\033[1;32m\\]${BREW_PROMPT_TYPE} \\[\\033[1;31m\\]\\w \\[\\033[1;34m\\]$\\[\\033[0m\\] " + +# Add the Homebrew PATH in front of the user's PATH +export PATH="${BREW_PROMPT_PATH}:${PATH}" +unset BREW_PROMPT_TYPE BREW_PROMPT_PATH diff --git a/Library/Homebrew/utils/shell.rb b/Library/Homebrew/utils/shell.rb index 4582542438..bb9396d5fd 100644 --- a/Library/Homebrew/utils/shell.rb +++ b/Library/Homebrew/utils/shell.rb @@ -153,14 +153,28 @@ module Utils str end - sig { params(type: String, preferred_path: String, notice: T.nilable(String)).returns(String) } - def shell_with_prompt(type, preferred_path:, notice:) + sig { params(type: String, preferred_path: String, notice: T.nilable(String), home: String).returns(String) } + def shell_with_prompt(type, preferred_path:, notice:, home: Dir.home) preferred = from_path(preferred_path) + path = ENV.fetch("PATH") subshell = case preferred when :zsh - "PROMPT='%B%F{green}#{type}%f %F{blue}$%f%b ' RPROMPT='[%B%F{red}%~%f%b]' #{preferred_path} -f" + zdotdir = Pathname.new(HOMEBREW_TEMP/"brew-zsh-prompt-#{Process.euid}") + zdotdir.mkpath + FileUtils.chmod_R(0700, zdotdir) + FileUtils.cp(HOMEBREW_LIBRARY_PATH/"utils/zsh/brew-sh-prompt-zshrc.zsh", zdotdir/".zshrc") + %w[.zcompdump .zsh_history .zsh_sessions].each do |file| + FileUtils.ln_sf("#{home}/#{file}", zdotdir/file) + end + <<~ZSH.strip + BREW_PROMPT_PATH="#{path}" BREW_PROMPT_TYPE="#{type}" ZDOTDIR="#{zdotdir}" #{preferred_path} + ZSH + when :bash + <<~BASH.strip + BREW_PROMPT_PATH="#{path}" BREW_PROMPT_TYPE="#{type}" #{preferred_path} --rcfile "#{HOMEBREW_LIBRARY_PATH}/utils/bash/brew-sh-prompt-bashrc.bash" + BASH else - "PS1=\"\\[\\033[1;32m\\]brew \\[\\033[1;31m\\]\\w \\[\\033[1;34m\\]$\\[\\033[0m\\] \" #{preferred_path}" + "PS1=\"\\[\\033[1;32m\\]#{type} \\[\\033[1;31m\\]\\w \\[\\033[1;34m\\]$\\[\\033[0m\\] \" #{preferred_path}" end puts notice if notice.present? diff --git a/Library/Homebrew/utils/zsh/brew-sh-prompt-zshrc.zsh b/Library/Homebrew/utils/zsh/brew-sh-prompt-zshrc.zsh new file mode 100644 index 0000000000..8ef8cda5ac --- /dev/null +++ b/Library/Homebrew/utils/zsh/brew-sh-prompt-zshrc.zsh @@ -0,0 +1,13 @@ +# Read the user's ~/.zshrc first +if [[ -f "${HOME}/.zshrc" ]] +then + source "${HOME}/.zshrc" +fi + +# Override the user's ZSH prompt with our custom prompt +export PROMPT="%B%F{green}${BREW_PROMPT_TYPE}%f %F{blue}$%f%b " +export RPROMPT="[%B%F{red}%~%f%b]" + +# Add the Homebrew PATH in front of the user's PATH +export PATH="${BREW_PROMPT_PATH}:${PATH}" +unset BREW_PROMPT_TYPE BREW_PROMPT_PATH