Fix overzealous /usr/local prefix replacement

Co-authored-by: MikeMcQuaid <125011+MikeMcQuaid@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-08-13 16:22:09 +01:00 committed by Mike McQuaid
parent 1d25414fee
commit 9328a55c54
No known key found for this signature in database
3 changed files with 77 additions and 7 deletions

View File

@ -539,8 +539,9 @@ module Homebrew
ohai "Detecting if #{local_filename} is relocatable..." if bottle_path.size > 1 * 1024 * 1024
prefix_check = if prefix == HOMEBREW_DEFAULT_PREFIX
File.join(prefix, "opt")
is_usr_local_prefix = prefix == "/usr/local"
prefix_check = if is_usr_local_prefix
"#{prefix}/opt"
else
prefix
end
@ -568,11 +569,17 @@ module Homebrew
relocatable = false if keg_contain?(prefix_check, keg, ignores, formula_and_runtime_deps_names)
relocatable = false if keg_contain?(cellar, keg, ignores, formula_and_runtime_deps_names)
relocatable = false if keg_contain?(HOMEBREW_LIBRARY.to_s, keg, ignores, formula_and_runtime_deps_names)
if prefix != prefix_check
if is_usr_local_prefix
relocatable = false if keg_contain_absolute_symlink_starting_with?(prefix, keg)
relocatable = false if keg_contain?("#{prefix}/etc", keg, ignores)
relocatable = false if keg_contain?("#{prefix}/var", keg, ignores)
relocatable = false if keg_contain?("#{prefix}/share/vim", keg, ignores)
if tap.disabled_new_usr_local_relocation_formulae.exclude?(formula.name)
keg.new_usr_local_replacement_pairs.each_value do |value|
relocatable = false if keg_contain?(value.fetch(:old), keg, ignores)
end
else
relocatable = false if keg_contain?("#{prefix}/etc", keg, ignores)
relocatable = false if keg_contain?("#{prefix}/var", keg, ignores)
relocatable = false if keg_contain?("#{prefix}/share/vim", keg, ignores)
end
end
skip_relocation = relocatable && !keg.require_relocation?
end

View File

@ -84,9 +84,37 @@ class Keg
JAVA_REGEX = %r{#{HOMEBREW_PREFIX}/opt/openjdk(@\d+(\.\d+)*)?/libexec(/openjdk\.jdk/Contents/Home)?}
sig { returns(T::Hash[Symbol, T::Hash[Symbol, String]]) }
def new_usr_local_replacement_pairs
{
prefix: {
old: "/usr/local/opt",
new: "#{PREFIX_PLACEHOLDER}/opt",
},
caskroom: {
old: "/usr/local/Caskroom",
new: "#{PREFIX_PLACEHOLDER}/Caskroom",
},
var_homebrew: {
old: "/usr/local/var/homebrew",
new: "#{PREFIX_PLACEHOLDER}/var/homebrew",
},
}
end
def prepare_relocation_to_placeholders
relocation = Relocation.new
relocation.add_replacement_pair(:prefix, HOMEBREW_PREFIX.to_s, PREFIX_PLACEHOLDER, path: true)
# Use selective HOMEBREW_PREFIX replacement when HOMEBREW_PREFIX=/usr/local
# This avoids overzealous replacement of system paths when a script refers to e.g. /usr/local/bin
if new_usr_local_relocation?
new_usr_local_replacement_pairs.each do |key, value|
relocation.add_replacement_pair(key, value.fetch(:old), value.fetch(:new), path: true)
end
else
relocation.add_replacement_pair(:prefix, HOMEBREW_PREFIX.to_s, PREFIX_PLACEHOLDER, path: true)
end
relocation.add_replacement_pair(:cellar, HOMEBREW_CELLAR.to_s, CELLAR_PLACEHOLDER, path: true)
# when HOMEBREW_PREFIX == HOMEBREW_REPOSITORY we should use HOMEBREW_PREFIX for all relocations to avoid
# being unable to differentiate between them.
@ -361,6 +389,25 @@ class Keg
def self.file_linked_libraries(_file, _string)
[]
end
private
sig { returns(T::Boolean) }
def new_usr_local_relocation?
return false if HOMEBREW_PREFIX.to_s != "/usr/local"
formula = begin
Formula[name]
rescue FormulaUnavailableError
nil
end
return true unless formula
tap = formula.tap
return true unless tap
tap.disabled_new_usr_local_relocation_formulae.exclude?(name)
end
end
require "extend/os/keg_relocate"

View File

@ -26,6 +26,8 @@ class Tap
private_constant :HOMEBREW_TAP_PYPI_FORMULA_MAPPINGS_FILE
HOMEBREW_TAP_SYNCED_VERSIONS_FORMULAE_FILE = "synced_versions_formulae.json"
private_constant :HOMEBREW_TAP_SYNCED_VERSIONS_FORMULAE_FILE
HOMEBREW_TAP_DISABLED_NEW_USR_LOCAL_RELOCATION_FORMULAE_FILE = "disabled_new_usr_local_relocation_formulae.json"
private_constant :HOMEBREW_TAP_DISABLED_NEW_USR_LOCAL_RELOCATION_FORMULAE_FILE
HOMEBREW_TAP_AUDIT_EXCEPTIONS_DIR = "audit_exceptions"
private_constant :HOMEBREW_TAP_AUDIT_EXCEPTIONS_DIR
HOMEBREW_TAP_STYLE_EXCEPTIONS_DIR = "style_exceptions"
@ -37,6 +39,7 @@ class Tap
#{HOMEBREW_TAP_MIGRATIONS_FILE}
#{HOMEBREW_TAP_PYPI_FORMULA_MAPPINGS_FILE}
#{HOMEBREW_TAP_SYNCED_VERSIONS_FORMULAE_FILE}
#{HOMEBREW_TAP_DISABLED_NEW_USR_LOCAL_RELOCATION_FORMULAE_FILE}
#{HOMEBREW_TAP_AUDIT_EXCEPTIONS_DIR}/*.json
#{HOMEBREW_TAP_STYLE_EXCEPTIONS_DIR}/*.json
].freeze, T::Array[String])
@ -1099,6 +1102,19 @@ class Tap
)
end
# Array with formulae that should not be relocated to new /usr/local
sig { overridable.returns(T::Array[String]) }
def disabled_new_usr_local_relocation_formulae
@disabled_new_usr_local_relocation_formulae ||= T.let(
if (synced_file = path/HOMEBREW_TAP_DISABLED_NEW_USR_LOCAL_RELOCATION_FORMULAE_FILE).file?
JSON.parse(synced_file.read)
else
[]
end,
T.nilable(T::Array[String]),
)
end
sig { returns(T::Boolean) }
def should_report_analytics?
installed? && !private?