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.
This commit is contained in:
Mike McQuaid committed 2026-09-13 08:45:07 +01:00
1 parent 0f32f8a600
commit d6fe073d4b
27 files changed
+458 -268

No files matched your search

+4
View File
@@ -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
-8
View File
@@ -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
+2 -29
View File
@@ -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:-}\";"
;;
+31 -27
View File
@@ -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]))
+18 -5
View File
@@ -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
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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) }
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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"
-5
View File
@@ -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
+1 -1
View File
@@ -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"),
+48 -21
View File
@@ -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(
@@ -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)
@@ -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
+84
View File
@@ -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"
+1 -1
View File
@@ -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
@@ -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
@@ -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
@@ -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|
+132
View File
@@ -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
-36
View File
@@ -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
-82
View File
@@ -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 <<EOS
conflicting Homebrew wrapper configuration!
HOMEBREW_FORCE_BREW_WRAPPER was set to ${HOMEBREW_FORCE_BREW_WRAPPER}
${CUSTOM_MESSAGE}
$(bold "Ensure you run ${HOMEBREW_FORCE_BREW_WRAPPER} directly (not ${HOMEBREW_ORIGINAL_BREW_FILE})")!
Manually setting your PATH can interfere with Homebrew wrappers.
Ensure your shell configuration contains:
eval "\$(${HOMEBREW_BREW_FILE} shellenv)"
or that ${HOMEBREW_FORCE_BREW_WRAPPER_WITHOUT_BREW} comes before ${HOMEBREW_PREFIX}/bin in your PATH:
export PATH="${HOMEBREW_FORCE_BREW_WRAPPER_WITHOUT_BREW}:${HOMEBREW_PREFIX}/bin:\$PATH"
EOS
if [[ -n "${HOMEBREW_FORCE_BREW_WRAPPER_HELP_MESSAGE:-}" ]]
then
echo "${HOMEBREW_FORCE_BREW_WRAPPER_HELP_MESSAGE}" >&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
}
-11
View File
@@ -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
+17 -7
View File
@@ -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.
-15
View File
@@ -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
+6 -2
View File
@@ -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.
-9
View File
@@ -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