Add support for Homebrew wrappers

Allow the ability for a system administrator to use
`HOMEBREW_BREW_WRAPPER` and `HOMEBREW_FORCE_BREW_WRAPPER` variables to
enforce the usage of a particular `brew` command for non-trivial (e.g.
`brew --prefix` is considered trivial, it doesn't need to write to the
prefix) Homebrew commands.

This also introduces a `HOMEBREW_ORIGINAL_BREW_FILE` variable for some
internal usage; `HOMEBREW_BREW_FILE` was being used internally for
both "how should we shell out to Homebrew" and "what should we use
to check permissions on Homebrew". `HOMEBREW_ORIGINAL_BREW_FILE` is
now used just for the latter case.

Inspired by conversation in
https://github.com/Homebrew/homebrew-bundle/pull/1551 which suggested
this was worth fixing in wider than just `brew bundle`.
This commit is contained in:
Mike McQuaid 2025-01-07 17:40:18 +00:00
parent d87d336c82
commit e9b4979f40
No known key found for this signature in database
11 changed files with 103 additions and 20 deletions

View File

@ -127,12 +127,6 @@ case "$1" in
homebrew-shellenv "$1" homebrew-shellenv "$1"
exit 0 exit 0
;; ;;
setup-ruby)
source "${HOMEBREW_LIBRARY}/Homebrew/cmd/setup-ruby.sh"
shift
homebrew-setup-ruby "$1"
exit 0
;;
esac esac
source "${HOMEBREW_LIBRARY}/Homebrew/help.sh" source "${HOMEBREW_LIBRARY}/Homebrew/help.sh"
@ -184,11 +178,56 @@ case "$@" in
;; ;;
esac esac
##### # Include some helper functions.
##### Next, define all helper functions.
#####
source "${HOMEBREW_LIBRARY}/Homebrew/utils/helpers.sh" source "${HOMEBREW_LIBRARY}/Homebrew/utils/helpers.sh"
# Require HOMEBREW_BREW_WRAPPER to be set if HOMEBREW_FORCE_BREW_WRAPPER is set
# for all non-trivial commands (i.e. not run above).
if [[ -n "${HOMEBREW_FORCE_BREW_WRAPPER}" ]]
then
if [[ -z "${HOMEBREW_BREW_WRAPPER:-}" ]]
then
odie <<EOS
HOMEBREW_FORCE_BREW_WRAPPER was set to
${HOMEBREW_FORCE_BREW_WRAPPER}
but HOMEBREW_BREW_WRAPPER was unset. This indicates that you are running
${HOMEBREW_BREW_FILE}
directly but should instead run
${HOMEBREW_FORCE_BREW_WRAPPER}
EOS
elif [[ "${HOMEBREW_FORCE_BREW_WRAPPER:-}" != "${HOMEBREW_BREW_WRAPPER:-}" ]]
then
odie <<EOS
HOMEBREW_FORCE_BREW_WRAPPER was set to
${HOMEBREW_FORCE_BREW_WRAPPER}
but HOMEBREW_BREW_WRAPPER was set to
${HOMEBREW_BREW_WRAPPER}
This indicates that you are running
${HOMEBREW_BREW_FILE}
directly but should instead run:
${HOMEBREW_FORCE_BREW_WRAPPER}
EOS
fi
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
# doesn't need a default case as other arguments handled elsewhere.
# shellcheck disable=SC2249
case "$1" in
setup-ruby)
source "${HOMEBREW_LIBRARY}/Homebrew/cmd/setup-ruby.sh"
shift
homebrew-setup-ruby "$1"
exit 0
;;
esac
#####
##### Next, define all other helper functions.
#####
check-run-command-as-root() { check-run-command-as-root() {
[[ "${EUID}" == 0 || "${UID}" == 0 ]] || return [[ "${EUID}" == 0 || "${UID}" == 0 ]] || return

View File

@ -87,6 +87,9 @@ module Homebrew
description: "Use this URL as the Homebrew/brew `git`(1) remote.", description: "Use this URL as the Homebrew/brew `git`(1) remote.",
default: HOMEBREW_BREW_DEFAULT_GIT_REMOTE, default: HOMEBREW_BREW_DEFAULT_GIT_REMOTE,
}, },
HOMEBREW_BREW_WRAPPER: {
description: "If set, use wrapper to call `brew` rather than auto-detecting it.",
},
HOMEBREW_BROWSER: { HOMEBREW_BROWSER: {
description: "Use this as the browser when opening project homepages.", description: "Use this as the browser when opening project homepages.",
default_text: "`$BROWSER` or the OS's default browser.", default_text: "`$BROWSER` or the OS's default browser.",
@ -242,6 +245,10 @@ module Homebrew
"Automatically set if the system version of `git` is too old.", "Automatically set if the system version of `git` is too old.",
boolean: true, boolean: true,
}, },
HOMEBREW_FORCE_BREW_WRAPPER: {
description: "If set, require `HOMEBREW_BREW_WRAPPER` to be set to the same value as " \
"`HOMEBREW_FORCE_BREW_WRAPPER` for non-trivial `brew` commands.",
},
HOMEBREW_FORCE_VENDOR_RUBY: { HOMEBREW_FORCE_VENDOR_RUBY: {
description: "If set, always use Homebrew's vendored, relocatable Ruby version even if the system version " \ description: "If set, always use Homebrew's vendored, relocatable Ruby version even if the system version " \
"of Ruby is new enough.", "of Ruby is new enough.",

View File

@ -1499,7 +1499,7 @@ class Formula
# @see .link_overwrite # @see .link_overwrite
def link_overwrite?(path) def link_overwrite?(path)
# Don't overwrite files not created by Homebrew. # Don't overwrite files not created by Homebrew.
return false if path.stat.uid != HOMEBREW_BREW_FILE.stat.uid return false if path.stat.uid != HOMEBREW_ORIGINAL_BREW_FILE.stat.uid
# Don't overwrite files belong to other keg except when that # Don't overwrite files belong to other keg except when that
# keg's formula is deleted. # keg's formula is deleted.

View File

@ -100,7 +100,7 @@ module Homebrew
end end
def owner_uid def owner_uid
@owner_uid ||= HOMEBREW_BREW_FILE.stat.uid @owner_uid ||= HOMEBREW_ORIGINAL_BREW_FILE.stat.uid
end end
def running_as_root_but_not_owned_by_root? def running_as_root_but_not_owned_by_root?

View File

@ -67,8 +67,8 @@ class Mktemp
# Reference from `man 2 open` # Reference from `man 2 open`
# > When a new file is created, it is given the group of the directory which # > When a new file is created, it is given the group of the directory which
# contains it. # contains it.
group_id = if HOMEBREW_BREW_FILE.grpowned? group_id = if HOMEBREW_ORIGINAL_BREW_FILE.grpowned?
HOMEBREW_BREW_FILE.stat.gid HOMEBREW_ORIGINAL_BREW_FILE.stat.gid
else else
Process.gid Process.gid
end end

View File

@ -103,7 +103,7 @@ class Sandbox
sig { void } sig { void }
def deny_write_homebrew_repository def deny_write_homebrew_repository
deny_write path: HOMEBREW_BREW_FILE deny_write path: HOMEBREW_ORIGINAL_BREW_FILE
if HOMEBREW_PREFIX.to_s == HOMEBREW_REPOSITORY.to_s if HOMEBREW_PREFIX.to_s == HOMEBREW_REPOSITORY.to_s
deny_write_path HOMEBREW_LIBRARY deny_write_path HOMEBREW_LIBRARY
deny_write_path HOMEBREW_REPOSITORY/".git" deny_write_path HOMEBREW_REPOSITORY/".git"

View File

@ -49,6 +49,9 @@ module Homebrew::EnvConfig
sig { returns(String) } sig { returns(String) }
def brew_git_remote; end def brew_git_remote; end
sig { returns(T.nilable(::String)) }
def brew_wrapper; end
sig { returns(T.nilable(::String)) } sig { returns(T.nilable(::String)) }
def browser; end def browser; end
@ -139,6 +142,9 @@ module Homebrew::EnvConfig
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def force_api_auto_update?; end def force_api_auto_update?; end
sig { returns(T::Boolean) }
def force_brew_wrapper?; end
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def force_brewed_ca_certificates?; end def force_brewed_ca_certificates?; end

View File

@ -4,6 +4,11 @@
raise "HOMEBREW_BREW_FILE was not exported! Please call bin/brew directly!" unless ENV["HOMEBREW_BREW_FILE"] raise "HOMEBREW_BREW_FILE was not exported! Please call bin/brew directly!" unless ENV["HOMEBREW_BREW_FILE"]
# Path to `bin/brew` main executable in `HOMEBREW_PREFIX` # 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
# Path to the executable that should be used to run `brew`.
# This may be HOMEBREW_ORIGINAL_BREW_FILE or HOMEBREW_BREW_WRAPPER.
HOMEBREW_BREW_FILE = Pathname(ENV.fetch("HOMEBREW_BREW_FILE")).freeze HOMEBREW_BREW_FILE = Pathname(ENV.fetch("HOMEBREW_BREW_FILE")).freeze
# Where we link under # Where we link under

View File

@ -304,7 +304,7 @@ module Homebrew
def self.shell_scripts def self.shell_scripts
[ [
HOMEBREW_BREW_FILE, HOMEBREW_ORIGINAL_BREW_FILE,
HOMEBREW_REPOSITORY/"completions/bash/brew", HOMEBREW_REPOSITORY/"completions/bash/brew",
HOMEBREW_REPOSITORY/"Dockerfile", HOMEBREW_REPOSITORY/"Dockerfile",
*HOMEBREW_REPOSITORY.glob(".devcontainer/**/*.sh"), *HOMEBREW_REPOSITORY.glob(".devcontainer/**/*.sh"),

View File

@ -3,6 +3,7 @@
raise "HOMEBREW_BREW_FILE was not exported! Please call bin/brew directly!" unless ENV["HOMEBREW_BREW_FILE"] 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 HOMEBREW_BREW_FILE = Pathname.new(ENV.fetch("HOMEBREW_BREW_FILE")).freeze
TEST_TMPDIR = ENV.fetch("HOMEBREW_TEST_TMPDIR") do |k| TEST_TMPDIR = ENV.fetch("HOMEBREW_TEST_TMPDIR") do |k|

View File

@ -116,6 +116,23 @@ unset BREW_FILE_DIRECTORY
# keg_relocate.rb, formula_cellar_checks.rb, and test/global_spec.rb need to change. # keg_relocate.rb, formula_cellar_checks.rb, and test/global_spec.rb need to change.
HOMEBREW_LIBRARY="${HOMEBREW_REPOSITORY}/Library" HOMEBREW_LIBRARY="${HOMEBREW_REPOSITORY}/Library"
# Use HOMEBREW_BREW_WRAPPER if set.
export HOMEBREW_ORIGINAL_BREW_FILE="${HOMEBREW_BREW_FILE}"
if [[ -n "${HOMEBREW_BREW_WRAPPER:-}" ]]
then
HOMEBREW_BREW_FILE="${HOMEBREW_BREW_WRAPPER}"
fi
# These variables are exported in this file and are not allowed to be overridden by the user.
BIN_BREW_EXPORTED_VARS=(
HOMEBREW_BREW_FILE
HOMEBREW_PREFIX
HOMEBREW_REPOSITORY
HOMEBREW_LIBRARY
HOMEBREW_USER_CONFIG_HOME
HOMEBREW_ORIGINAL_BREW_FILE
)
# Load Homebrew's variable configuration files from disk. # Load Homebrew's variable configuration files from disk.
export_homebrew_env_file() { export_homebrew_env_file() {
local env_file local env_file
@ -126,6 +143,15 @@ export_homebrew_env_file() {
do do
# only load HOMEBREW_* lines # only load HOMEBREW_* lines
[[ "${line}" = "HOMEBREW_"* ]] || continue [[ "${line}" = "HOMEBREW_"* ]] || continue
# forbid overriding variables that are set in this file
local invalid_variable
for VAR in "${BIN_BREW_EXPORTED_VARS[@]}"
do
[[ "${line}" = "${VAR}"* ]] && invalid_variable="${VAR}"
done
[[ -n "${invalid_variable}" ]] && continue
export "${line?}" export "${line?}"
done <"${env_file}" done <"${env_file}"
} }
@ -212,11 +238,10 @@ done
unset VAR VAR_NEW MANPAGE_VARS USED_BY_HOMEBREW_VARS unset VAR VAR_NEW MANPAGE_VARS USED_BY_HOMEBREW_VARS
export HOMEBREW_BREW_FILE for VAR in "${BIN_BREW_EXPORTED_VARS[@]}"
export HOMEBREW_PREFIX do
export HOMEBREW_REPOSITORY export "${VAR?}"
export HOMEBREW_LIBRARY done
export HOMEBREW_USER_CONFIG_HOME
# set from user environment # set from user environment
# shellcheck disable=SC2154 # shellcheck disable=SC2154