diff --git a/Library/Homebrew/dev-cmd/bottle.rb b/Library/Homebrew/dev-cmd/bottle.rb index 0a6d847e46..662739e91f 100644 --- a/Library/Homebrew/dev-cmd/bottle.rb +++ b/Library/Homebrew/dev-cmd/bottle.rb @@ -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 diff --git a/Library/Homebrew/keg_relocate.rb b/Library/Homebrew/keg_relocate.rb index 648297ef68..3e75672cd8 100644 --- a/Library/Homebrew/keg_relocate.rb +++ b/Library/Homebrew/keg_relocate.rb @@ -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" diff --git a/Library/Homebrew/tap.rb b/Library/Homebrew/tap.rb index 989a1fff1d..b3a0855ba4 100644 --- a/Library/Homebrew/tap.rb +++ b/Library/Homebrew/tap.rb @@ -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?