From d6fe073d4bd5b933faa09fba3e5a8b8997398715 Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Sat, 12 Sep 2026 09:20:14 +0100 Subject: [PATCH] Deprecate Homebrew wrapper configuration - Classify third-party wrappers as Tier 3 and centralise their support guidance in `Support-Tiers.md`. - Retire required wrappers, custom help and the opt-out together. - Check Bash-only environment deprecations before Ruby commands, reusing the per-process warning cache in `EnvConfig`. - Leave Ruby-backed environment deprecations to their accessors. - Remove shell validation, conflict errors and custom help output. - Invoke internal commands through Homebrew, not the wrapper, and remove private parent-process inspection and executable overrides. - Prepend Homebrew's `bin` and `sbin` without reading custom path files. - Warn when another `brew` shadows this installation, sharing the existing PATH diagnostics and shell-specific remediation. --- Library/Homebrew/brew.rb | 4 + Library/Homebrew/brew.sh | 8 -- Library/Homebrew/cmd/shellenv.sh | 31 +--- Library/Homebrew/diagnostic.rb | 58 ++++---- Library/Homebrew/env_config.rb | 23 ++- Library/Homebrew/formula.rb | 2 +- Library/Homebrew/global.rb | 2 +- Library/Homebrew/mktemp.rb | 4 +- Library/Homebrew/sandbox.rb | 4 +- Library/Homebrew/startup/config.rb | 5 - Library/Homebrew/style.rb | 2 +- Library/Homebrew/test/cmd/exec_spec.rb | 69 ++++++--- Library/Homebrew/test/cmd/shellenv_spec.rb | 15 ++ .../test/diagnostic/brew_path_spec.rb | 83 +++++++++++ Library/Homebrew/test/env_config_spec.rb | 84 +++++++++++ Library/Homebrew/test/sandbox_shared_spec.rb | 2 +- .../spec/shared_context/integration_test.rb | 2 +- .../shared_context/integration_test_spec.rb | 10 +- .../test/support/lib/startup/config.rb | 1 - Library/Homebrew/test/utils/wrapper_spec.rb | 132 ++++++++++++++++++ Library/Homebrew/utils/pid_path.rb | 36 ----- Library/Homebrew/utils/wrapper.sh | 82 ----------- bin/brew | 11 -- docs/Deprecating-Disabling-and-Removing.md | 24 +++- docs/Manpage.md | 15 -- docs/Support-Tiers.md | 8 +- manpages/brew.1 | 9 -- 27 files changed, 458 insertions(+), 268 deletions(-) create mode 100644 Library/Homebrew/test/diagnostic/brew_path_spec.rb create mode 100644 Library/Homebrew/test/utils/wrapper_spec.rb delete mode 100755 Library/Homebrew/utils/pid_path.rb delete mode 100644 Library/Homebrew/utils/wrapper.sh diff --git a/Library/Homebrew/brew.rb b/Library/Homebrew/brew.rb index cbd9d66934..b77fd84261 100644 --- a/Library/Homebrew/brew.rb +++ b/Library/Homebrew/brew.rb @@ -122,6 +122,10 @@ begin # `Homebrew::Help.help` never returns, except for unknown and deferred commands. end + if !help_flag && (internal_cmd || external_ruby_v2_cmd || external_ruby_cmd_path || external_cmd_path) + Homebrew::EnvConfig.check_deprecated_bash_variables + end + if cmd.nil? raise UsageError, "Unknown command: brew #{ARGV.join(" ")}" elsif internal_cmd || external_ruby_v2_cmd diff --git a/Library/Homebrew/brew.sh b/Library/Homebrew/brew.sh index 4b70ef53cd..64eec5ad58 100644 --- a/Library/Homebrew/brew.sh +++ b/Library/Homebrew/brew.sh @@ -133,14 +133,6 @@ case "$@" in ;; esac -# Check `HOMEBREW_FORCE_BREW_WRAPPER` for all non-trivial commands -# (i.e. not defined above this line e.g. formulae or --cellar). -if [[ -n "${HOMEBREW_FORCE_BREW_WRAPPER:-}" ]] -then - source "${HOMEBREW_LIBRARY}/Homebrew/utils/wrapper.sh" - check-brew-wrapper "$1" -fi - # commands that take a single or no arguments and need to write to HOMEBREW_PREFIX. # HOMEBREW_LIBRARY set by bin/brew # shellcheck disable=SC2154 diff --git a/Library/Homebrew/cmd/shellenv.sh b/Library/Homebrew/cmd/shellenv.sh index 582eba771d..1bcce3dcdd 100644 --- a/Library/Homebrew/cmd/shellenv.sh +++ b/Library/Homebrew/cmd/shellenv.sh @@ -28,23 +28,6 @@ homebrew-shellenv() { HOMEBREW_SHELL_NAME="${SHELL##*/}" fi - if [[ -n "${HOMEBREW_MACOS}" ]] && - [[ "${HOMEBREW_MACOS_VERSION_NUMERIC}" -ge "140000" ]] && - [[ -x /usr/libexec/path_helper ]] - then - HOMEBREW_PATHS_FILE="${HOMEBREW_PREFIX}/etc/paths" - - if [[ ! -f "${HOMEBREW_PATHS_FILE}" ]] - then - printf '%s/bin\n%s/sbin\n' "${HOMEBREW_PREFIX}" "${HOMEBREW_PREFIX}" 2>/dev/null >"${HOMEBREW_PATHS_FILE}" - fi - - if [[ -r "${HOMEBREW_PATHS_FILE}" ]] - then - PATH_HELPER_ROOT="${HOMEBREW_PREFIX}" - fi - fi - case "${HOMEBREW_SHELL_NAME}" in fish | -fish) echo "set --global --export HOMEBREW_PREFIX \"${HOMEBREW_PREFIX}\";" @@ -58,12 +41,7 @@ homebrew-shellenv() { echo "setenv HOMEBREW_PREFIX ${HOMEBREW_PREFIX};" echo "setenv HOMEBREW_CELLAR ${HOMEBREW_CELLAR};" echo "setenv HOMEBREW_REPOSITORY ${HOMEBREW_REPOSITORY};" - if [[ -n "${PATH_HELPER_ROOT}" ]] - then - echo "eval \`/usr/bin/env PATH_HELPER_ROOT=\"${PATH_HELPER_ROOT}\" /usr/libexec/path_helper -c\`;" - else - echo "setenv PATH \"${HOMEBREW_PREFIX}/bin:${HOMEBREW_PREFIX}/sbin:\$PATH\";" - fi + echo "setenv PATH \"${HOMEBREW_PREFIX}/bin:${HOMEBREW_PREFIX}/sbin:\$PATH\";" echo "test \${?MANPATH} -eq 1 && test -n \"\${MANPATH}\" && setenv MANPATH :\`printf '%s' \"\${MANPATH}\" | /usr/bin/sed -e 's/^:*//' -e 's/:*\$//'\`;" echo "test \${?INFOPATH} -eq 1 || setenv INFOPATH '';" echo "setenv INFOPATH \"${HOMEBREW_PREFIX}/share/info:\${INFOPATH}\";" @@ -85,12 +63,7 @@ homebrew-shellenv() { echo "fpath[1,0]=\"${HOMEBREW_PREFIX}/share/zsh/site-functions\";" echo "export FPATH;" fi - if [[ -n "${PATH_HELPER_ROOT}" ]] - then - echo "eval \"\$(/usr/bin/env PATH_HELPER_ROOT=\"${PATH_HELPER_ROOT}\" /usr/libexec/path_helper -s)\"" - else - echo "export PATH=\"${HOMEBREW_PREFIX}/bin:${HOMEBREW_PREFIX}/sbin\${PATH+:\$PATH}\";" - fi + echo "export PATH=\"${HOMEBREW_PREFIX}/bin:${HOMEBREW_PREFIX}/sbin\${PATH+:\$PATH}\";" echo "[ -z \"\${MANPATH-}\" ] || { export MANPATH=\"\${MANPATH%\"\${MANPATH##*[!:]}\"}\"; export MANPATH=\":\${MANPATH#\"\${MANPATH%%[!:]*}\"}\"; };" echo "export INFOPATH=\"${HOMEBREW_PREFIX}/share/info:\${INFOPATH:-}\";" ;; diff --git a/Library/Homebrew/diagnostic.rb b/Library/Homebrew/diagnostic.rb index 517e872c5d..e9370ad129 100644 --- a/Library/Homebrew/diagnostic.rb +++ b/Library/Homebrew/diagnostic.rb @@ -519,13 +519,28 @@ module Homebrew ) end + sig { returns(T.nilable(Finding)) } + def check_brew_path + brew = which("brew", paths) + return if brew.nil? || File.identical?(brew, HOMEBREW_BREW_FILE) + + Finding.new( + <<~EOS, + Another `brew` shadows this Homebrew installation in your PATH: + #{brew} + + This may be a helper, wrapper or another Homebrew installation. + EOS + remediation: path_remediation, + ) + end + sig { returns(T.nilable(Finding)) } def check_user_path_1 @seen_prefix_bin = false @seen_prefix_sbin = false message = "" - remediation = T.let(nil, T.nilable(Finding::Remediation)) paths.each do |p| case p @@ -545,15 +560,6 @@ module Homebrew The following tools exist at both paths: EOS - prepend_path = Utils::Shell.prepend_path_in_profile("#{HOMEBREW_PREFIX}/bin") - remediation = Finding::Remediation.new( - text: <<~EOS, - Consider setting your PATH so that - #{HOMEBREW_PREFIX}/bin occurs before /usr/bin. Here is a one-liner: - #{prepend_path} - EOS - commands: [prepend_path].compact, - ) end end when "#{HOMEBREW_PREFIX}/bin" @@ -564,7 +570,7 @@ module Homebrew end @user_path_1_done = true - Finding.new(message, remediation:) if message.present? + Finding.new(message, remediation: path_remediation) if message.present? end sig { returns(T.nilable(Finding)) } @@ -572,18 +578,11 @@ module Homebrew check_user_path_1 unless @user_path_1_done return if @seen_prefix_bin - prepend_path = Utils::Shell.prepend_path_in_profile("#{HOMEBREW_PREFIX}/bin") Finding.new( <<~EOS, Homebrew's "bin" was not found in your PATH. EOS - remediation: Finding::Remediation.new( - text: <<~EOS, - Consider setting your PATH for example like so: - #{prepend_path} - EOS - commands: [prepend_path].compact, - ), + remediation: path_remediation, ) end @@ -598,19 +597,12 @@ module Homebrew return if sbin.children.empty? return if sbin.children.one? && sbin.children.first.basename.to_s == ".keepme" - prepend_path = Utils::Shell.prepend_path_in_profile("#{HOMEBREW_PREFIX}/sbin") Finding.new( <<~EOS, Homebrew's "sbin" was not found in your PATH but you have installed formulae that put executables in #{HOMEBREW_PREFIX}/sbin. EOS - remediation: Finding::Remediation.new( - text: <<~EOS, - Consider setting your PATH for example like so: - #{prepend_path} - EOS - commands: [prepend_path].compact, - ), + remediation: path_remediation(sbin.to_s), ) end @@ -1643,6 +1635,18 @@ module Homebrew private + sig { params(path: String).returns(Finding::Remediation) } + def path_remediation(path = "#{HOMEBREW_PREFIX}/bin") + prepend_path = Utils::Shell.prepend_path_in_profile(path) + Finding::Remediation.new( + text: <<~EOS, + Consider setting your PATH for example like so: + #{prepend_path} + EOS + commands: [prepend_path].compact, + ) + end + sig { returns(T::Array[String]) } def paths @paths ||= T.let(ORIGINAL_PATHS.uniq.map(&:to_s), T.nilable(T::Array[String])) diff --git a/Library/Homebrew/env_config.rb b/Library/Homebrew/env_config.rb index 94d9e23da1..944315a88a 100644 --- a/Library/Homebrew/env_config.rb +++ b/Library/Homebrew/env_config.rb @@ -431,12 +431,14 @@ module Homebrew boolean: :set, }, HOMEBREW_FORCE_BREW_WRAPPER: { - description: "If set, require `brew` to be invoked by the value of " \ - "`$HOMEBREW_FORCE_BREW_WRAPPER` for non-trivial `brew` commands.", + description: "No longer used.", + replacement: "your wrapper directly", + odeprecated: true, }, HOMEBREW_FORCE_BREW_WRAPPER_HELP_MESSAGE: { - description: "If set, appended to the `$HOMEBREW_FORCE_BREW_WRAPPER` error message to provide " \ - "additional help or context to the user.", + description: "No longer used.", + replacement: "custom help in your wrapper", + odeprecated: true, }, HOMEBREW_FORCE_VENDOR_RUBY: { description: "If set, always use Homebrew's vendored, relocatable Ruby version even if the system version " \ @@ -577,8 +579,10 @@ module Homebrew odisabled: true, }, HOMEBREW_NO_FORCE_BREW_WRAPPER: { - description: "`Deprecated:` If set, disables `$HOMEBREW_FORCE_BREW_WRAPPER` behaviour, even if set.", + description: "No longer used.", boolean: :set, + replacement: "an environment without $HOMEBREW_NO_FORCE_BREW_WRAPPER", + odeprecated: true, }, HOMEBREW_NO_GITHUB_API: { description: "If set, do not use the GitHub API, e.g. for searches or fetching relevant issues " \ @@ -931,6 +935,15 @@ module Homebrew end end + sig { void } + def self.check_deprecated_bash_variables + force_brew_wrapper + force_brew_wrapper_help_message + no_force_brew_wrapper? + + nil + end + sig { params(env: T.any(String, Symbol), hash: T::Hash[Symbol, T.untyped]).returns(T.nilable(String)) } def env_value(env, hash) env = env.to_s diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 3ea3accfe3..a9a5e1e2ab 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -828,7 +828,7 @@ class Formula sig { params(path: Pathname).returns(T.nilable(T.any(String, Symbol))) } def link_overwrite_keg_name(path) # Don't overwrite files not created by Homebrew. - return if path.stat.uid != HOMEBREW_ORIGINAL_BREW_FILE.stat.uid + return if path.stat.uid != HOMEBREW_BREW_FILE.stat.uid keg = Keg.for(path) # This keg doesn't belong to any current core/tap formula, most likely coming from a DIY install. diff --git a/Library/Homebrew/global.rb b/Library/Homebrew/global.rb index 378e13336a..077289ed29 100644 --- a/Library/Homebrew/global.rb +++ b/Library/Homebrew/global.rb @@ -114,7 +114,7 @@ module Homebrew sig { returns(Integer) } def owner_uid - @owner_uid ||= T.let(HOMEBREW_ORIGINAL_BREW_FILE.stat.uid, T.nilable(Integer)) + @owner_uid ||= T.let(HOMEBREW_BREW_FILE.stat.uid, T.nilable(Integer)) end sig { returns(T::Boolean) } diff --git a/Library/Homebrew/mktemp.rb b/Library/Homebrew/mktemp.rb index fca1aa9e18..74f9c27d2f 100644 --- a/Library/Homebrew/mktemp.rb +++ b/Library/Homebrew/mktemp.rb @@ -119,8 +119,8 @@ class Mktemp # Reference from `man 2 open` # > When a new file is created, it is given the group of the directory which # contains it. - group_id = if HOMEBREW_ORIGINAL_BREW_FILE.grpowned? - HOMEBREW_ORIGINAL_BREW_FILE.stat.gid + group_id = if HOMEBREW_BREW_FILE.grpowned? + HOMEBREW_BREW_FILE.stat.gid else Process.gid end diff --git a/Library/Homebrew/sandbox.rb b/Library/Homebrew/sandbox.rb index 17854cb130..016ace194d 100644 --- a/Library/Homebrew/sandbox.rb +++ b/Library/Homebrew/sandbox.rb @@ -237,7 +237,7 @@ class Sandbox executable_path = Pathname.new(executable_name) return PATH.new(executable_path.dirname) if executable_path.absolute? - PATH.new(ORIGINAL_PATHS, ENV.fetch("PATH"), HOMEBREW_ORIGINAL_BREW_FILE.dirname) + PATH.new(ORIGINAL_PATHS, ENV.fetch("PATH"), HOMEBREW_BREW_FILE.dirname) end sig { returns(T.nilable(Pathname)) } @@ -541,7 +541,7 @@ class Sandbox sig { void } def deny_write_homebrew_repository - deny_write path: HOMEBREW_ORIGINAL_BREW_FILE + deny_write path: HOMEBREW_BREW_FILE if HOMEBREW_PREFIX.to_s == HOMEBREW_REPOSITORY.to_s deny_write_path HOMEBREW_LIBRARY deny_write_path HOMEBREW_REPOSITORY/".git" diff --git a/Library/Homebrew/startup/config.rb b/Library/Homebrew/startup/config.rb index be996fe86a..ebf1fe1d80 100644 --- a/Library/Homebrew/startup/config.rb +++ b/Library/Homebrew/startup/config.rb @@ -4,7 +4,6 @@ raise "HOMEBREW_BREW_FILE was not exported! Please call bin/brew directly!" unless ENV["HOMEBREW_BREW_FILE"] # The path to the executable that should be used to run `brew`. -# This may be HOMEBREW_ORIGINAL_BREW_FILE depending on the system configuration. # Favour this instead of running `brew` and expecting it to be in the `PATH`. # @api public HOMEBREW_BREW_FILE = Pathname(ENV.fetch("HOMEBREW_BREW_FILE")).freeze @@ -31,10 +30,6 @@ HOMEBREW_TEMP = Pathname(ENV.fetch("HOMEBREW_TEMP")).then do |tmp| tmp.realpath end.freeze -# Path to `bin/brew` main executable in `HOMEBREW_PREFIX` -# Used for e.g. permissions checks. -HOMEBREW_ORIGINAL_BREW_FILE = Pathname(ENV.fetch("HOMEBREW_ORIGINAL_BREW_FILE")).freeze - # Where `.git` is found HOMEBREW_REPOSITORY = Pathname(ENV.fetch("HOMEBREW_REPOSITORY")).freeze diff --git a/Library/Homebrew/style.rb b/Library/Homebrew/style.rb index b17db3af0c..40dfc7717d 100644 --- a/Library/Homebrew/style.rb +++ b/Library/Homebrew/style.rb @@ -505,7 +505,7 @@ module Homebrew sig { returns(T::Array[Pathname]) } def self.shell_scripts [ - HOMEBREW_ORIGINAL_BREW_FILE.realpath, + HOMEBREW_BREW_FILE.realpath, HOMEBREW_REPOSITORY/"completions/bash/brew", HOMEBREW_REPOSITORY/"Dockerfile", *HOMEBREW_REPOSITORY.glob(".devcontainer/**/*.sh"), diff --git a/Library/Homebrew/test/cmd/exec_spec.rb b/Library/Homebrew/test/cmd/exec_spec.rb index 6b7ab28ecd..bf7aaf3a15 100644 --- a/Library/Homebrew/test/cmd/exec_spec.rb +++ b/Library/Homebrew/test/cmd/exec_spec.rb @@ -18,20 +18,44 @@ RSpec.describe Homebrew::Cmd::Exec do let(:env_executable) { shell_cellar/"#{env_formula_name}/1.0/bin/#{env_executable_name}" } let(:installable_formula_name) { "test-installable" } let(:installable_executable_name) { "test-installable-tool" } - let(:brew_wrapper) { HOMEBREW_TEMP/"brew-exec-wrapper/brew" } - let(:inline_script) { HOMEBREW_TEMP/"brew-exec-wrapper/script.sh" } + let(:fake_brew) { HOMEBREW_TEMP/"brew-exec-test/brew" } + let(:inline_script) { HOMEBREW_TEMP/"brew-exec-test/script.sh" } let(:brew_sh_env) do { - "HOMEBREW_BREW_SH" => (HOMEBREW_PREFIX/"bin/brew").to_s, - "HOMEBREW_FORCE_BREW_WRAPPER" => brew_wrapper.to_s, - "HOMEBREW_NO_FORCE_BREW_WRAPPER" => "1", - "HOMEBREW_TEMP" => HOMEBREW_TEMP.to_s, - "HOMEBREW_COLOR" => nil, - "GITHUB_ACTIONS" => nil, + "HOMEBREW_BREW_SH" => (HOMEBREW_PREFIX/"bin/brew").to_s, + "HOMEBREW_TEMP" => HOMEBREW_TEMP.to_s, + "HOMEBREW_COLOR" => nil, + "GITHUB_ACTIONS" => nil, } end + sig { params(args: String).returns(SystemCommand::Result) } + def exec_command(*args) + result = SystemCommand.run("/bin/bash", args: [ + "-c", <<~SH, + source "$HOMEBREW_LIBRARY/Homebrew/utils/os.sh" + source "$HOMEBREW_LIBRARY/Homebrew/utils.sh" + source "$1" + shift + homebrew-exec "$@" + SH + "bash", HOMEBREW_LIBRARY_PATH/"cmd/exec.sh", *args + ], print_stderr: false) + $stdout.print result.stdout + $stderr.print result.stderr + result + end + before do + ENV["HOMEBREW_BREW_FILE"] = fake_brew.to_s + ENV["HOMEBREW_LIBRARY"] = HOMEBREW_LIBRARY_PATH.parent.to_s + ENV["HOMEBREW_PREFIX"] = HOMEBREW_PREFIX.to_s + ENV["HOMEBREW_CELLAR"] = shell_cellar.to_s + ENV["HOMEBREW_CACHE"] = HOMEBREW_CACHE.to_s + ENV["HOMEBREW_TEMP"] = HOMEBREW_TEMP.to_s + ENV.delete("HOMEBREW_COLOR") + ENV.delete("GITHUB_ACTIONS") + (HOMEBREW_PREFIX/"bin").mkpath FileUtils.ln_sf HOMEBREW_LIBRARY_PATH.parent.parent/"bin/brew", HOMEBREW_PREFIX/"bin/brew" db.dirname.mkpath @@ -62,7 +86,7 @@ RSpec.describe Homebrew::Cmd::Exec do linked_executable.write("#!/bin/sh\necho linked-provider\n") FileUtils.chmod 0755, linked_executable - brew_wrapper.dirname.mkpath + fake_brew.dirname.mkpath inline_script.write(<<~SH) #!/bin/sh #{executable_name} "$@" @@ -70,7 +94,7 @@ RSpec.describe Homebrew::Cmd::Exec do SH FileUtils.chmod 0755, inline_script - brew_wrapper.write(<<~SH) + fake_brew.write(<<~SH) #!/bin/sh case "$1" in deps) @@ -88,12 +112,12 @@ RSpec.describe Homebrew::Cmd::Exec do ln -sfn "#{shell_cellar}/#{installable_formula_name}/1.0.0" "#{HOMEBREW_PREFIX}/opt/#{installable_formula_name}" ;; *) - echo "unexpected brew wrapper call: $*" >&2 + echo "unexpected brew call: $*" >&2 exit 1 ;; esac SH - FileUtils.chmod 0755, brew_wrapper + FileUtils.chmod 0755, fake_brew end after do @@ -104,49 +128,52 @@ RSpec.describe Homebrew::Cmd::Exec do FileUtils.rm_rf HOMEBREW_PREFIX/"opt/#{env_formula_name}" FileUtils.rm_rf HOMEBREW_PREFIX/"opt/#{installable_formula_name}" FileUtils.rm_f HOMEBREW_PREFIX/"bin/#{executable_name}" - FileUtils.rm_rf brew_wrapper.dirname + FileUtils.rm_rf fake_brew.dirname end it "runs commands in formula environments and supports the x alias", :aggregate_failures, :integration_test do expect do - expect(brew_sh("exec", executable_name, "arg", brew_sh_env)).to be_a_success + expect(brew_sh("exec", executable_name, "arg", brew_sh_env)) + .to be_a_success end.to( output("active-version arg\n").to_stdout .and(output("").to_stderr), ) expect do - expect(brew_sh("x", executable_name, brew_sh_env)).to be_a_success + expect(brew_sh("x", executable_name, brew_sh_env)) + .to be_a_success end.to( output("active-version\n").to_stdout .and(output("").to_stderr), ) + end + it "runs commands with explicit formulae and installs missing providers", :aggregate_failures do expect do - expect(brew_sh("exec", "--formulae=#{formula_name}, #{env_formula_name}", "--", inline_script.to_s, "arg", - brew_sh_env)).to be_a_success + expect(exec_command("--formulae=#{formula_name}, #{env_formula_name}", "--", inline_script.to_s, "arg")) + .to be_a_success end.to( output("active-version arg\nenv-version arg\n").to_stdout .and(output("").to_stderr), ) expect do - expect(brew_sh("exec", "--formulae=", executable_name, brew_sh_env)).to be_a_failure + expect(exec_command("--formulae=", executable_name)).to be_a_failure end.to( output("").to_stdout .and(output("Error: `--formulae` requires a comma-separated formula list.\n").to_stderr), ) expect do - expect(brew_sh("exec", "--sandbox=", executable_name, brew_sh_env)).to be_a_failure + expect(exec_command("--sandbox=", executable_name)).to be_a_failure end.to( output("").to_stdout .and(output("Error: `--sandbox` requires a writable path.\n").to_stderr), ) expect do - expect(brew_sh("exec", installable_executable_name, "arg", - brew_sh_env)).to be_a_success + expect(exec_command(installable_executable_name, "arg")).to be_a_success end.to( output("installable-version arg\n").to_stdout .and( diff --git a/Library/Homebrew/test/cmd/shellenv_spec.rb b/Library/Homebrew/test/cmd/shellenv_spec.rb index 9dda939e7e..94566ce4e8 100644 --- a/Library/Homebrew/test/cmd/shellenv_spec.rb +++ b/Library/Homebrew/test/cmd/shellenv_spec.rb @@ -37,6 +37,7 @@ RSpec.describe "brew shellenv", type: :system do sig { params(shell: String, shell_args: T::Array[String]).returns(T::Hash[String, String]) } def shellenv_environment(shell, shell_args) stdout, stderr, status = Open3.capture3( + { "HOMEBREW_PREFIX" => nil, "HOMEBREW_CELLAR" => nil, "HOMEBREW_REPOSITORY" => nil }, (which(shell, ORIGINAL_PATHS) || skip("#{shell} is not installed")).to_s, *shell_args, "#{shellenv(shell)}\n/usr/bin/env" ) @@ -92,6 +93,20 @@ RSpec.describe "brew shellenv", type: :system do .and end_with("/custom path:/usr/bin:/bin") end + it "ignores customised path_helper configuration in #{shell}" do + ENV["HOMEBREW_MACOS"] = "1" + ENV["HOMEBREW_MACOS_VERSION_NUMERIC"] = "140000" + ENV["PATH_HELPER_ROOT"] = HOMEBREW_PREFIX.to_s + ENV["PATH"] = "/usr/bin:/bin:/wrapper/bin" + expected_path = shellenv_environment(shell, shell_args).fetch("PATH") + (HOMEBREW_PREFIX/"etc/paths.d").mkpath + (HOMEBREW_PREFIX/"etc/paths").write("/wrapper/bin\n#{HOMEBREW_PREFIX}/bin\n#{HOMEBREW_PREFIX}/sbin\n") + (HOMEBREW_PREFIX/"etc/paths.d/wrapper").write("/another/wrapper/bin\n") + + expect(shellenv_environment(shell, shell_args).fetch("PATH")) + .to eq(expected_path) + end + it "preserves existing FPATH entries and adds completions for #{shell}" do expect(shellenv_environment(shell, shell_args).fetch("FPATH").split(":")) .to eq([("#{HOMEBREW_PREFIX}/share/zsh/site-functions" if shell == "zsh"), "/custom/functions"].compact) diff --git a/Library/Homebrew/test/diagnostic/brew_path_spec.rb b/Library/Homebrew/test/diagnostic/brew_path_spec.rb new file mode 100644 index 0000000000..50676c947d --- /dev/null +++ b/Library/Homebrew/test/diagnostic/brew_path_spec.rb @@ -0,0 +1,83 @@ +# typed: strict +# frozen_string_literal: true + +require "diagnostic" + +RSpec.describe Homebrew::Diagnostic::Checks do + describe "#check_brew_path" do + sig { returns(Homebrew::Diagnostic::Checks) } + subject(:checks) { described_class.new } + + sig { returns(Pathname) } + def brew + HOMEBREW_PREFIX/"bin/brew" + end + + sig { returns(Pathname) } + def other_brew + HOMEBREW_TEMP/"wrapper/bin/brew" + end + + before do + brew.dirname.mkpath + brew.write("#!/bin/sh\n") + brew.chmod 0755 + other_brew.dirname.mkpath + other_brew.write("#!/bin/sh\nexit 1\n") + other_brew.chmod 0755 + stub_const("HOMEBREW_BREW_FILE", brew) + stub_const("ORIGINAL_PATHS", [other_brew.dirname, brew.dirname]) + end + + it "warns when another brew shadows this installation" do + expect(checks.check_brew_path&.to_s) + .to include("Another `brew` shadows this Homebrew installation in your PATH:", + other_brew.to_s, "#{HOMEBREW_PREFIX}/bin") + end + + it "checks the original PATH, not Homebrew's modified PATH" do + ENV["PATH"] = brew.dirname.to_s + + expect(checks.check_brew_path).not_to be_nil + end + + it "uses the same PATH guidance as the missing-bin warning" do + stub_const("ORIGINAL_PATHS", [other_brew.dirname]) + + expect(checks.check_brew_path&.remediation&.to_h) + .to eq(checks.check_user_path_2&.remediation&.to_h) + end + + it "does not warn when this installation takes precedence" do + stub_const("ORIGINAL_PATHS", [brew.dirname, other_brew.dirname]) + + expect(checks.check_brew_path).to be_nil + end + + it "does not warn about symlinks to the same brew" do + other_brew.unlink + FileUtils.ln_s brew, other_brew + + expect(checks.check_brew_path).to be_nil + end + + it "ignores non-executable files" do + other_brew.chmod 0644 + + expect(checks.check_brew_path).to be_nil + end + + it "ignores directories named brew" do + other_brew.unlink + other_brew.mkpath + + expect(checks.check_brew_path).to be_nil + end + + it "does not duplicate the missing PATH warning" do + stub_const("ORIGINAL_PATHS", []) + + expect(checks.check_brew_path).to be_nil + end + end +end diff --git a/Library/Homebrew/test/env_config_spec.rb b/Library/Homebrew/test/env_config_spec.rb index 818f7e8ed3..50e53e5b26 100644 --- a/Library/Homebrew/test/env_config_spec.rb +++ b/Library/Homebrew/test/env_config_spec.rb @@ -70,6 +70,63 @@ RSpec.describe Homebrew::EnvConfig do end end + describe ".check_deprecated_bash_variables" do + before do + ENV.delete("HOMEBREW_FORCE_BREW_WRAPPER") + ENV.delete("HOMEBREW_FORCE_BREW_WRAPPER_HELP_MESSAGE") + ENV.delete("HOMEBREW_NO_FORCE_BREW_WRAPPER") + end + + test_each(%w[ + HOMEBREW_FORCE_BREW_WRAPPER + HOMEBREW_FORCE_BREW_WRAPPER_HELP_MESSAGE + HOMEBREW_NO_FORCE_BREW_WRAPPER + ]) do |variable| + it "deprecates the obsolete Bash setting #{variable}" do + ENV[variable] = "1" + + expect { env_config.check_deprecated_bash_variables } + .to raise_error(MethodDeprecatedError, /#{variable}.*deprecated/) + end + end + + it "leaves Ruby settings to their accessors" do + ENV["HOMEBREW_BAT_THEME"] = "GitHub" + ENV["HOMEBREW_PRY"] = "1" + ENV["HOMEBREW_REQUIRE_TAP_TRUST"] = "1" + + expect { env_config.check_deprecated_bash_variables }.not_to raise_error + end + + it "ignores blank values" do + ENV["HOMEBREW_FORCE_BREW_WRAPPER"] = "" + + expect { env_config.check_deprecated_bash_variables }.not_to raise_error + end + + it "warns once per variable even when checked or read again" do + ENV["HOMEBREW_FORCE_BREW_WRAPPER"] = "/wrapper/brew" + ENV["HOMEBREW_NO_FORCE_BREW_WRAPPER"] = "0" + ENV.delete("HOMEBREW_TESTS") + ENV.delete("HOMEBREW_DEVELOPER") + ENV.delete("GITHUB_ACTIONS") + Homebrew.raise_deprecation_exceptions = false + allow(Homebrew).to receive(:auditing?).and_return(false) + stub_const("Homebrew::EnvConfig::WARNED_DEPRECATED_ENVS", Set.new) + + expect do + env_config.check_deprecated_bash_variables + env_config.check_deprecated_bash_variables + env_config.force_brew_wrapper + env_config.no_force_brew_wrapper? + end.to output( + "Warning: Calling HOMEBREW_FORCE_BREW_WRAPPER is deprecated! Use your wrapper directly instead.\n" \ + "Warning: Calling HOMEBREW_NO_FORCE_BREW_WRAPPER is deprecated! " \ + "Use an environment without $HOMEBREW_NO_FORCE_BREW_WRAPPER instead.\n", + ).to_stderr + end + end + describe ".allowed_taps" do it "deprecates the tap allowlist" do ENV["HOMEBREW_ALLOWED_TAPS"] = "user/repo" @@ -97,6 +154,33 @@ RSpec.describe Homebrew::EnvConfig do end end + describe ".force_brew_wrapper" do + it "deprecates requiring a wrapper" do + ENV["HOMEBREW_FORCE_BREW_WRAPPER"] = "/wrapper/brew" + + expect { env_config.force_brew_wrapper } + .to raise_error(MethodDeprecatedError, /HOMEBREW_FORCE_BREW_WRAPPER.*deprecated/) + end + end + + describe ".force_brew_wrapper_help_message" do + it "deprecates custom wrapper help" do + ENV["HOMEBREW_FORCE_BREW_WRAPPER_HELP_MESSAGE"] = "Contact support." + + expect { env_config.force_brew_wrapper_help_message } + .to raise_error(MethodDeprecatedError, /HOMEBREW_FORCE_BREW_WRAPPER_HELP_MESSAGE.*deprecated/) + end + end + + describe ".no_force_brew_wrapper?" do + it "deprecates the wrapper opt-out" do + ENV["HOMEBREW_NO_FORCE_BREW_WRAPPER"] = "1" + + expect { env_config.no_force_brew_wrapper? } + .to raise_error(MethodDeprecatedError, /HOMEBREW_NO_FORCE_BREW_WRAPPER.*deprecated/) + end + end + describe ".arch" do it "deprecates overriding the compiler architecture" do ENV["HOMEBREW_ARCH"] = "haswell" diff --git a/Library/Homebrew/test/sandbox_shared_spec.rb b/Library/Homebrew/test/sandbox_shared_spec.rb index 9d7b45d588..15c9ef0922 100644 --- a/Library/Homebrew/test/sandbox_shared_spec.rb +++ b/Library/Homebrew/test/sandbox_shared_spec.rb @@ -289,7 +289,7 @@ RSpec.describe Sandbox do before do sandbox_class.test_executable_name = executable_name sandbox_class.unsuitable_executables = [] - stub_const("HOMEBREW_ORIGINAL_BREW_FILE", homebrew_bin/"brew") + stub_const("HOMEBREW_BREW_FILE", homebrew_bin/"brew") end it "uses the first suitable executable candidate" do diff --git a/Library/Homebrew/test/support/helper/spec/shared_context/integration_test.rb b/Library/Homebrew/test/support/helper/spec/shared_context/integration_test.rb index e3d00c0a60..8ad6d0e90b 100644 --- a/Library/Homebrew/test/support/helper/spec/shared_context/integration_test.rb +++ b/Library/Homebrew/test/support/helper/spec/shared_context/integration_test.rb @@ -142,7 +142,7 @@ module Test test_prefix_library.mkpath FileUtils.ln_sf HOMEBREW_LIBRARY_PATH, test_prefix_library/"Homebrew" # `cp` would keep the touched file's non-executable mode. - FileUtils.install HOMEBREW_ORIGINAL_BREW_FILE, HOMEBREW_PREFIX/"bin/brew", mode: 0755 + FileUtils.install HOMEBREW_BREW_FILE, HOMEBREW_PREFIX/"bin/brew", mode: 0755 (HOMEBREW_PREFIX/"bin/brew").to_s end diff --git a/Library/Homebrew/test/support/helper/spec/shared_context/integration_test_spec.rb b/Library/Homebrew/test/support/helper/spec/shared_context/integration_test_spec.rb index b73037d1a6..dd783cef9b 100644 --- a/Library/Homebrew/test/support/helper/spec/shared_context/integration_test_spec.rb +++ b/Library/Homebrew/test/support/helper/spec/shared_context/integration_test_spec.rb @@ -14,7 +14,15 @@ RSpec.describe Test::Helper::IntegrationTest do (HOMEBREW_PREFIX/"bin").mkpath example.run ensure - FileUtils.rm_rf HOMEBREW_PREFIX/"bin" + FileUtils.rm_rf [HOMEBREW_PREFIX/"bin", HOMEBREW_PREFIX/"Library/Homebrew"] + end + + it "copies an executable Homebrew entry point into the test prefix" do + FileUtils.touch HOMEBREW_PREFIX/"bin/brew" + brew_sh_path = Pathname(helper.test_prefix_brew_sh) + + expect([brew_sh_path.symlink?, brew_sh_path.executable?, brew_sh_path.read]) + .to eq([false, true, HOMEBREW_BREW_FILE.read]) end it "disables Sorbet checking in Ruby integration commands" do diff --git a/Library/Homebrew/test/support/lib/startup/config.rb b/Library/Homebrew/test/support/lib/startup/config.rb index 1e3f00c64a..a0e48aca0b 100644 --- a/Library/Homebrew/test/support/lib/startup/config.rb +++ b/Library/Homebrew/test/support/lib/startup/config.rb @@ -3,7 +3,6 @@ raise "HOMEBREW_BREW_FILE was not exported! Please call bin/brew directly!" unless ENV["HOMEBREW_BREW_FILE"] -HOMEBREW_ORIGINAL_BREW_FILE = Pathname.new(ENV.fetch("HOMEBREW_ORIGINAL_BREW_FILE")).freeze HOMEBREW_BREW_FILE = Pathname.new(ENV.fetch("HOMEBREW_BREW_FILE")).freeze TEST_TMPDIR = ENV.fetch("HOMEBREW_TEST_TMPDIR") do |k| diff --git a/Library/Homebrew/test/utils/wrapper_spec.rb b/Library/Homebrew/test/utils/wrapper_spec.rb new file mode 100644 index 0000000000..4c96560628 --- /dev/null +++ b/Library/Homebrew/test/utils/wrapper_spec.rb @@ -0,0 +1,132 @@ +# typed: strict +# frozen_string_literal: true + +RSpec.describe "brew wrapper", type: :system do + sig { returns(SystemCommand::Result) } + subject(:result) { SystemCommand.run(HOMEBREW_BREW_FILE, args: ["commands", "--quiet"]) } + + before do + ENV["HOMEBREW_FORCE_BREW_WRAPPER"] = "/wrapper/brew" + ENV["HOMEBREW_BREW_WRAPPER"] = "/wrapper/brew" + ENV.delete("HOMEBREW_NO_FORCE_BREW_WRAPPER") + ENV["HOMEBREW_NO_INSTALL_FROM_API"] = "1" + ENV.delete("HOMEBREW_FORCE_BREW_WRAPPER_HELP_MESSAGE") + ENV.delete("HOMEBREW_DEVELOPER") + ENV["HOMEBREW_NO_COLOR"] = "1" + end + + it "warns about obsolete wrapper configuration" do + expect([result.status.exitstatus, result.stderr]).to eq([ + 0, + "Warning: Calling HOMEBREW_FORCE_BREW_WRAPPER is deprecated! " \ + "Use your wrapper directly instead.\n", + ]) + end + + test_each([nil, "0"]) do |opt_out| + it "keeps the Homebrew executable through a wrapper with opt-out #{opt_out.inspect}" do + wrapper = HOMEBREW_TEMP/"brew-wrapper" + wrapper.write(<<~SH) + #!/bin/bash + export HOMEBREW_BREW_WRAPPER="$0" + exec "#{HOMEBREW_BREW_FILE}" "$@" + SH + FileUtils.chmod 0755, wrapper + ENV["HOMEBREW_FORCE_BREW_WRAPPER"] = wrapper.to_s + ENV["HOMEBREW_NO_FORCE_BREW_WRAPPER"] = opt_out + result = SystemCommand.run(wrapper, args: ["ruby", "-e", "puts HOMEBREW_BREW_FILE"]) + + expect([result.status.exitstatus, result.stdout]).to eq([0, "#{HOMEBREW_BREW_FILE}\n"]) + end + end + + it "turns deprecations into errors for developers" do + ENV["HOMEBREW_DEVELOPER"] = "1" + + expect([result.status.exitstatus, result.stderr.lines.first]).to eq([ + 1, + "Error: Calling HOMEBREW_FORCE_BREW_WRAPPER is deprecated! Use your wrapper directly instead.\n", + ]) + end + + test_each([nil, "/another/brew"]) do |wrapper| + it "does not require a matching wrapper when the marker is #{wrapper.inspect}" do + ENV["HOMEBREW_BREW_WRAPPER"] = wrapper + + expect(result).to be_a_success + end + end + + it "warns about obsolete custom help without printing it" do + ENV.delete("HOMEBREW_FORCE_BREW_WRAPPER") + ENV["HOMEBREW_FORCE_BREW_WRAPPER_HELP_MESSAGE"] = "-n" + + expect([result.status.exitstatus, result.stderr]).to eq([ + 0, + "Warning: Calling HOMEBREW_FORCE_BREW_WRAPPER_HELP_MESSAGE is deprecated! " \ + "Use custom help in your wrapper instead.\n", + ]) + end + + it "warns about the obsolete opt-out without requiring a wrapper" do + ENV.delete("HOMEBREW_FORCE_BREW_WRAPPER") + ENV["HOMEBREW_NO_FORCE_BREW_WRAPPER"] = "0" + + expect([result.status.exitstatus, result.stderr]).to eq([ + 0, + "Warning: Calling HOMEBREW_NO_FORCE_BREW_WRAPPER is deprecated! " \ + "Use an environment without $HOMEBREW_NO_FORCE_BREW_WRAPPER instead.\n", + ]) + end + + it "does nothing when no wrapper is required" do + ENV.delete("HOMEBREW_FORCE_BREW_WRAPPER") + + expect([result.status.exitstatus, result.stderr]).to eq([0, ""]) + end + + it "does not check deprecated Ruby settings that the command does not read" do + ENV.delete("HOMEBREW_FORCE_BREW_WRAPPER") + ENV["HOMEBREW_BAT_THEME"] = "GitHub" + + expect([result.status.exitstatus, result.stderr]).to eq([0, ""]) + end + + it "does not check deprecated settings when displaying help" do + ENV["HOMEBREW_DEVELOPER"] = "1" + result = SystemCommand.run(HOMEBREW_BREW_FILE, args: ["commands", "--help"]) + + expect([result.status.exitstatus, result.stderr]).to eq([0, ""]) + end + + it "does not check deprecated settings when an external command displays its own help" do + command = HOMEBREW_TEMP/"brew-wrapper-test" + command.write(<<~SH) + #!/bin/bash + printf '%s\\n' "$@" + SH + command.chmod(0755) + ENV["HOMEBREW_PATH"] = PATH.new(ENV.fetch("PATH")).prepend(HOMEBREW_TEMP).to_s + ENV["HOMEBREW_DEVELOPER"] = "1" + result = SystemCommand.run(HOMEBREW_BREW_FILE, args: ["help", "wrapper-test"]) + + expect([result.status.exitstatus, result.stdout, result.stderr]).to eq([0, "--help\n", ""]) + end + + it "reports unknown commands before checking deprecated settings" do + ENV["HOMEBREW_DEVELOPER"] = "1" + result = SystemCommand.run(HOMEBREW_BREW_FILE, args: ["wrapper-test-unknown"]) + + expect([result.status.exitstatus, result.stderr.lines.grep(/Error:|Warning:/)]).to eq([ + 1, + ["Error: Invalid usage: Unknown command: brew wrapper-test-unknown\n"], + ]) + end + + it "does not warn or enforce wrapper settings in shell-only commands" do + ENV.delete("HOMEBREW_BREW_WRAPPER") + result = SystemCommand.run(HOMEBREW_BREW_FILE, args: ["--version"]) + + expect([result.status.exitstatus, result.stderr]).to eq([0, ""]) + end +end diff --git a/Library/Homebrew/utils/pid_path.rb b/Library/Homebrew/utils/pid_path.rb deleted file mode 100755 index 225b6c5b00..0000000000 --- a/Library/Homebrew/utils/pid_path.rb +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env ruby -# typed: strict -# frozen_string_literal: true - -pid = ARGV[0]&.to_i -raise "Missing `pid` argument!" unless pid - -require "fiddle" - -# Canonically, this is a part of libproc.dylib. libproc is however just a symlink to libSystem -# and some security tools seem to not support aliases from the dyld shared cache and incorrectly flag this. -libproc = Fiddle.dlopen("/usr/lib/libSystem.B.dylib") - -libproc_proc_pidpath_function = Fiddle::Function.new( - libproc["proc_pidpath"], - [Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP, Fiddle::TYPE_UINT32_T], - Fiddle::TYPE_INT, -) - -# We have to allocate a (char) buffer of exactly `PROC_PIDPATHINFO_MAXSIZE` to use `proc_pidpath` -# From `include/sys/proc_info.h`, PROC_PIDPATHINFO_MAXSIZE = 4 * MAXPATHLEN -# From `include/sys/param.h`, MAXPATHLEN = PATH_MAX -# From `include/sys/syslimits.h`, PATH_MAX = 1024 -# https://github.com/apple-oss-distributions/xnu/blob/e3723e1f17661b24996789d8afc084c0c3303b26/libsyscall/wrappers/libproc/libproc.c#L268-L275 -buffer_size = 4 * 1024 # PROC_PIDPATHINFO_MAXSIZE = 4 * MAXPATHLEN -buffer = "\0" * buffer_size -pointer_to_buffer = Fiddle::Pointer.to_ptr(buffer) - -# `proc_pidpath` returns a positive value on success. See: -# https://stackoverflow.com/a/8149198 -# https://github.com/chromium/chromium/blob/86df41504a235f9369f6f53887da12a718a19db4/base/process/process_handle_mac.cc#L37-L44 -# https://github.com/apple-oss-distributions/xnu/blob/e3723e1f17661b24996789d8afc084c0c3303b26/libsyscall/wrappers/libproc/libproc.c#L263-L283 -return_value = libproc_proc_pidpath_function.call(pid, pointer_to_buffer, buffer_size) -raise "Call to `proc_pidpath` failed! `proc_pidpath` returned #{return_value}." unless return_value.positive? - -puts pointer_to_buffer.to_s.strip diff --git a/Library/Homebrew/utils/wrapper.sh b/Library/Homebrew/utils/wrapper.sh deleted file mode 100644 index 14df09b39b..0000000000 --- a/Library/Homebrew/utils/wrapper.sh +++ /dev/null @@ -1,82 +0,0 @@ -# `brew` wrapper handling helpers. - -# HOMEBREW_LIBRARY, HOMEBREW_BREW_FILE, HOMEBREW_ORIGINAL_BREW_FILE, HOMEBREW_PREFIX are set by bin/brew. -# HOMEBREW_FORCE_BREW_WRAPPER is set by the user environment. -# shellcheck disable=SC2154 -odie-with-wrapper-message() { - source "${HOMEBREW_LIBRARY}/Homebrew/utils.sh" - - local CUSTOM_MESSAGE="${1}" - local HOMEBREW_FORCE_BREW_WRAPPER_WITHOUT_BREW="${HOMEBREW_FORCE_BREW_WRAPPER%/brew}" - - onoe <&2 - fi - - exit 1 -} - -check-brew-wrapper() { - [[ -z "${HOMEBREW_FORCE_BREW_WRAPPER:-}" ]] && return - [[ -z "${HOMEBREW_DISABLE_NO_FORCE_BREW_WRAPPER:-}" && -n "${HOMEBREW_NO_FORCE_BREW_WRAPPER:-}" ]] && return - - # Require HOMEBREW_BREW_WRAPPER to be set if HOMEBREW_FORCE_BREW_WRAPPER is set - # (and HOMEBREW_NO_FORCE_BREW_WRAPPER and HOMEBREW_DISABLE_NO_FORCE_BREW_WRAPPER are not set). - if [[ -z "${HOMEBREW_DISABLE_NO_FORCE_BREW_WRAPPER:-}" && -z "${HOMEBREW_NO_FORCE_BREW_WRAPPER:-}" ]] - then - if [[ -z "${HOMEBREW_BREW_WRAPPER:-}" ]] - then - odie-with-wrapper-message "but HOMEBREW_BREW_WRAPPER was unset." - elif [[ "${HOMEBREW_FORCE_BREW_WRAPPER}" != "${HOMEBREW_BREW_WRAPPER}" ]] - then - odie-with-wrapper-message "but HOMEBREW_BREW_WRAPPER was set to ${HOMEBREW_BREW_WRAPPER}" - fi - - return - fi - - # If HOMEBREW_FORCE_BREW_WRAPPER and HOMEBREW_DISABLE_NO_FORCE_BREW_WRAPPER are set, - # verify that the path to our parent process is the same as the value of HOMEBREW_FORCE_BREW_WRAPPER. - # We can't check this for `brew vendor-install` as this would cause an infinite loop on macOS. - if [[ -n "${HOMEBREW_DISABLE_NO_FORCE_BREW_WRAPPER:-}" && "$1" != "vendor-install" ]] - then - local HOMEBREW_BREW_CALLER HOMEBREW_BREW_CALLER_CHECK_EXIT_CODE - - if [[ -n "${HOMEBREW_MACOS:-}" ]] - then - source "${HOMEBREW_LIBRARY}/Homebrew/utils/ruby.sh" - setup-ruby-path - HOMEBREW_BREW_CALLER="$("${HOMEBREW_RUBY_PATH}" "${HOMEBREW_LIBRARY}/Homebrew/utils/pid_path.rb" "${PPID}")" - else - HOMEBREW_BREW_CALLER="$(readlink -f "/proc/${PPID}/exe")" - fi - HOMEBREW_BREW_CALLER_CHECK_EXIT_CODE="$?" - - if ((HOMEBREW_BREW_CALLER_CHECK_EXIT_CODE != 0)) - then - source "${HOMEBREW_LIBRARY}/Homebrew/utils.sh" - # Error message already printed above when populating `HOMEBREW_BREW_CALLER`. - odie "failed to check the path to the parent process!" - fi - - if [[ "${HOMEBREW_BREW_CALLER:-}" != "${HOMEBREW_FORCE_BREW_WRAPPER}" ]] - then - odie-with-wrapper-message "but \`brew\` was invoked by ${HOMEBREW_BREW_CALLER}." - fi - fi -} diff --git a/bin/brew b/bin/brew index 4ac6c6624f..42f1afb119 100755 --- a/bin/brew +++ b/bin/brew @@ -120,7 +120,6 @@ BIN_BREW_EXPORTED_VARS=( HOMEBREW_REPOSITORY HOMEBREW_LIBRARY HOMEBREW_USER_CONFIG_HOME - HOMEBREW_ORIGINAL_BREW_FILE ) printf -v BIN_BREW_EXPORTED_VARS_REGEX '%s|' "${BIN_BREW_EXPORTED_VARS[@]}" BIN_BREW_EXPORTED_VARS_REGEX="^(${BIN_BREW_EXPORTED_VARS_REGEX%|})(=|$)" @@ -150,9 +149,6 @@ export_homebrew_env_file() { done <"${env_file}" } -# We only want to be able to set this in `brew.env` files. -unset HOMEBREW_DISABLE_NO_FORCE_BREW_WRAPPER - # First, load the system-wide configuration. export_homebrew_env_file "/etc/homebrew/brew.env" @@ -184,13 +180,6 @@ then export_homebrew_env_file "/etc/homebrew/brew.env" fi -# Use HOMEBREW_FORCE_BREW_WRAPPER if set. -export HOMEBREW_ORIGINAL_BREW_FILE="${HOMEBREW_BREW_FILE}" -if [[ -n "${HOMEBREW_FORCE_BREW_WRAPPER:-}" ]] -then - HOMEBREW_BREW_FILE="${HOMEBREW_FORCE_BREW_WRAPPER}" -fi - # Record which HOMEBREW_* variables the user set (including via brew.env # above) before brew exports more itself, e.g. HOMEBREW_EDITOR from # EDITOR/VISUAL below or HOMEBREW_UPDATE_TO_TAG in cmd/update.sh, so diff --git a/docs/Deprecating-Disabling-and-Removing.md b/docs/Deprecating-Disabling-and-Removing.md index efef59ff97..8074a041d9 100644 --- a/docs/Deprecating-Disabling-and-Removing.md +++ b/docs/Deprecating-Disabling-and-Removing.md @@ -210,14 +210,24 @@ When `odeprecated: true`, the flag is hidden from `--help` and completions and p ### Environment variables -Environment variables follow the same four-stage lifecycle. Include migration logic to copy the old value during the transition: +Environment variables follow the same four-stage lifecycle. +Define deprecations in `Homebrew::EnvConfig::ENVS` in `Library/Homebrew/env_config.rb`: ```ruby -if (old_value = ENV["HOMEBREW_OLD_VAR"].presence) - opoo "`HOMEBREW_OLD_VAR` is deprecated. Use `HOMEBREW_NEW_VAR` instead." - # odeprecated "HOMEBREW_OLD_VAR", "HOMEBREW_NEW_VAR" - ENV["HOMEBREW_NEW_VAR"] = old_value -end +{ + HOMEBREW_OLD_VAR: { + description: "Use this value for the old setting.", + replacement: :HOMEBREW_NEW_VAR, + # odeprecated: true, + }, +} ``` -The `# odeprecated` comment is then uncommented, changed to `odisabled` and finally removed following the standard lifecycle. +The `# odeprecated: true` comment is then uncommented, changed to `odisabled: true` and finally removed with the variable definition following the standard lifecycle. +A symbol `replacement:` copies the old value to the replacement variable if that variable is unset; a string provides migration guidance without copying the value. + +Ruby accessors report deprecations when variables are read. +For deprecated variables previously consumed only by Bash, add their accessors to `Homebrew::EnvConfig.check_deprecated_bash_variables` so Ruby commands still report their deprecations. +This check runs before a recognised command is executed. +Each variable's warning is printed at most once per Ruby process. +Help and unknown commands do not run this check, and shell-only commands do not load Ruby for it. diff --git a/docs/Manpage.md b/docs/Manpage.md index 374b0fca84..b5d3d6ae05 100644 --- a/docs/Manpage.md +++ b/docs/Manpage.md @@ -4774,16 +4774,6 @@ command execution (e.g. `$(cat file)`). : If set, always use a Homebrew-installed `git`(1) rather than the system version. Automatically set if the system version of `git` is too old. -`HOMEBREW_FORCE_BREW_WRAPPER` - -: If set, require `brew` to be invoked by the value of - `$HOMEBREW_FORCE_BREW_WRAPPER` for non-trivial `brew` commands. - -`HOMEBREW_FORCE_BREW_WRAPPER_HELP_MESSAGE` - -: If set, appended to the `$HOMEBREW_FORCE_BREW_WRAPPER` error message to - provide additional help or context to the user. - `HOMEBREW_FORCE_VENDOR_RUBY` : If set, always use Homebrew's vendored, relocatable Ruby version even if the @@ -4941,11 +4931,6 @@ command execution (e.g. `$(cat file)`). : If set, do not print any hints about changing Homebrew's behaviour with environment variables. -`HOMEBREW_NO_FORCE_BREW_WRAPPER` - -: `Deprecated:` If set, disables `$HOMEBREW_FORCE_BREW_WRAPPER` behaviour, even - if set. - `HOMEBREW_NO_GITHUB_API` : If set, do not use the GitHub API, e.g. for searches or fetching relevant diff --git a/docs/Support-Tiers.md b/docs/Support-Tiers.md index 0a1138ea69..81cb56bcca 100644 --- a/docs/Support-Tiers.md +++ b/docs/Support-Tiers.md @@ -92,10 +92,16 @@ Tier 3 configurations include: - Systems that build official packages from source despite available bottles - Homebrew installed outside the default prefix (e.g. `/opt/homebrew`, `/usr/local` or `/home/linuxbrew/.linuxbrew` used on mismatched architectures) - Homebrew installations managed by Nix (e.g. nix-darwin or nix-homebrew) +- Homebrew invoked through a third-party wrapper - Installing formulae using `--HEAD` - Installing deprecated or disabled formulae - Intel x86_64 systems running macOS +If you are using a Homebrew wrapper, get support from and file issues with that wrapper instead of Homebrew unless the same problem is reproducible when running Homebrew directly. +Homebrew's internal `brew` commands invoke Homebrew directly, not the wrapper. +The legacy `HOMEBREW_FORCE_BREW_WRAPPER`, `HOMEBREW_FORCE_BREW_WRAPPER_HELP_MESSAGE` and `HOMEBREW_NO_FORCE_BREW_WRAPPER` settings are ignored and produce deprecation warnings in Ruby commands. +`brew doctor` warns if another `brew` executable takes precedence over the current installation in your `PATH`. + ## Unsupported An unsupported configuration is one in which: @@ -121,8 +127,6 @@ While Homebrew may assist third-party maintainers in resolving issues related to Bugs that occur only when using third-party formulae or casks may be closed without investigation. -If you are using a Homebrew wrapper, get support from and file issues with that wrapper instead of Homebrew unless the same problem is reproducible when running Homebrew directly. - ## Future macOS support macOS Tahoe 26 is the final version of macOS to run on Intel hardware. In alignment with this change, Homebrew has stopped building new bottles for Intel systems, and will remove the ability to run Homebrew on Intel systems in or after September 2027. diff --git a/manpages/brew.1 b/manpages/brew.1 index 36850b715c..e964118696 100644 --- a/manpages/brew.1 +++ b/manpages/brew.1 @@ -3073,12 +3073,6 @@ If set, always use a Homebrew\-installed \fBcurl\fP(1) rather than the system ve \fBHOMEBREW_FORCE_BREWED_GIT\fP If set, always use a Homebrew\-installed \fBgit\fP(1) rather than the system version\. Automatically set if the system version of \fBgit\fP is too old\. .TP -\fBHOMEBREW_FORCE_BREW_WRAPPER\fP -If set, require \fBbrew\fP to be invoked by the value of \fB$HOMEBREW_FORCE_BREW_WRAPPER\fP for non\-trivial \fBbrew\fP commands\. -.TP -\fBHOMEBREW_FORCE_BREW_WRAPPER_HELP_MESSAGE\fP -If set, appended to the \fB$HOMEBREW_FORCE_BREW_WRAPPER\fP error message to provide additional help or context to the user\. -.TP \fBHOMEBREW_FORCE_VENDOR_RUBY\fP If set, always use Homebrew\[u2019]s vendored, relocatable Ruby version even if the system version of Ruby is new enough\. .TP @@ -3194,9 +3188,6 @@ If set, do not print \fB$HOMEBREW_INSTALL_BADGE\fP on a successful build\. \fBHOMEBREW_NO_ENV_HINTS\fP If set, do not print any hints about changing Homebrew\[u2019]s behaviour with environment variables\. .TP -\fBHOMEBREW_NO_FORCE_BREW_WRAPPER\fP -\fBDeprecated:\fP If set, disables \fB$HOMEBREW_FORCE_BREW_WRAPPER\fP behaviour, even if set\. -.TP \fBHOMEBREW_NO_GITHUB_API\fP If set, do not use the GitHub API, e\.g\. for searches or fetching relevant issues after a failed install\. .TP