Merge pull request #20494 from Homebrew/keg_relocate_typed_strict

**/keg_relocate.rb: add set Sorbet `typed: strict`
This commit is contained in:
Mike McQuaid 2025-08-18 15:01:55 +00:00 committed by GitHub
commit 8b0b8a8b1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 72 additions and 26 deletions

View File

@ -590,7 +590,7 @@ module Homebrew
ensure
ignore_interrupts do
original_tab&.write
keg.replace_placeholders_with_locations changed_files unless args.skip_relocation?
keg.replace_placeholders_with_locations(changed_files) if changed_files && !args.skip_relocation?
end
end
end

View File

@ -1,9 +1,10 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "compilers"
class Keg
sig { params(relocation: Relocation, skip_protodesc_cold: T::Boolean).void }
def relocate_dynamic_linkage(relocation, skip_protodesc_cold: false)
# Patching the dynamic linker of glibc breaks it.
return if name.match? Version.formula_optionally_versioned_regex(:glibc)
@ -17,6 +18,10 @@ class Keg
end
end
sig {
params(file: Pathname, old_prefix: T.any(String, Regexp), new_prefix: String,
skip_protodesc_cold: T::Boolean).returns(T::Boolean)
}
def change_rpath!(file, old_prefix, new_prefix, skip_protodesc_cold: false)
return false if !file.elf? || !file.dynamic_elf?
@ -59,6 +64,7 @@ class Keg
true
end
sig { params(options: T::Hash[Symbol, T::Boolean]).returns(T::Array[Symbol]) }
def detect_cxx_stdlibs(options = {})
skip_executables = options.fetch(:skip_executables, false)
results = Set.new
@ -73,6 +79,7 @@ class Keg
results.to_a
end
sig { returns(T::Array[Pathname]) }
def elf_files
hardlinks = Set.new
elf_files = []

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:disable Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
module OS
@ -9,6 +9,7 @@ module OS
requires_ancestor { ::Keg }
module ClassMethods
sig { params(file: Pathname, string: String).returns(T::Array[String]) }
def file_linked_libraries(file, string)
# Check dynamic library linkage. Importantly, do not perform for static
# libraries, which will falsely report "linkage" to themselves.
@ -20,6 +21,7 @@ module OS
end
end
sig { params(relocation: ::Keg::Relocation, skip_protodesc_cold: T::Boolean).void }
def relocate_dynamic_linkage(relocation, skip_protodesc_cold: false)
mach_o_files.each do |file|
file.ensure_writable do
@ -50,6 +52,7 @@ module OS
end
end
sig { void }
def fix_dynamic_linkage
mach_o_files.each do |file|
file.ensure_writable do
@ -98,6 +101,7 @@ module OS
super
end
sig { params(file: Pathname, target: String).returns(String) }
def loader_name_for(file, target)
# Use @loader_path-relative install names for other Homebrew-installed binaries.
if ENV["HOMEBREW_RELOCATABLE_INSTALL_NAMES"] && target.start_with?(HOMEBREW_PREFIX)
@ -113,6 +117,7 @@ module OS
# If file is a dylib or bundle itself, look for the dylib named by
# bad_name relative to the lib directory, so that we can skip the more
# expensive recursive search if possible.
sig { params(file: Pathname, bad_name: String).returns(String) }
def fixed_name(file, bad_name)
if bad_name.start_with? ::Keg::PREFIX_PLACEHOLDER
bad_name.sub(::Keg::PREFIX_PLACEHOLDER, HOMEBREW_PREFIX)
@ -132,14 +137,16 @@ module OS
end
end
VARIABLE_REFERENCE_RX = /^@(loader_|executable_|r)path/
VARIABLE_REFERENCE_RX = T.let(/^@(loader_|executable_|r)path/, Regexp)
sig { params(file: Pathname, linkage_type: Symbol, resolve_variable_references: T::Boolean, block: T.proc.params(arg0: String).void).void }
def each_linkage_for(file, linkage_type, resolve_variable_references: false, &block)
file.public_send(linkage_type, resolve_variable_references:)
.grep_v(VARIABLE_REFERENCE_RX)
.each(&block)
end
sig { params(file: Pathname).returns(String) }
def dylib_id_for(file)
# Swift dylib IDs should be /usr/lib/swift
return file.dylib_id if file.dylib_id.start_with?("/usr/lib/swift/libswift")
@ -167,14 +174,16 @@ module OS
# `XXX.framework/XXX`, both with or without a slash-delimited prefix.
FRAMEWORK_RX = %r{(?:^|/)(([^/]+)\.framework/(?:Versions/[^/]+/)?\2)$}
sig { params(bad_name: String).returns(String) }
def find_dylib_suffix_from(bad_name)
if (framework = bad_name.match(FRAMEWORK_RX))
framework[1]
T.must(framework[1])
else
File.basename(bad_name)
end
end
sig { params(bad_name: String).returns(T.nilable(Pathname)) }
def find_dylib(bad_name)
return unless lib.directory?
@ -182,6 +191,7 @@ module OS
lib.find { |pn| break pn if pn.to_s.end_with?(suffix) }
end
sig { returns(T::Array[Pathname]) }
def mach_o_files
hardlinks = Set.new
mach_o_files = []
@ -198,6 +208,7 @@ module OS
mach_o_files
end
sig { returns(::Keg::Relocation) }
def prepare_relocation_to_locations
relocation = super
@ -225,12 +236,14 @@ module OS
relocation
end
sig { returns(String) }
def recursive_fgrep_args
# Don't recurse into symlinks; the man page says this is the default, but
# it's wrong. -O is a BSD-grep-only option.
"-lrO"
end
sig { returns([String, String]) }
def egrep_args
grep_bin = "egrep"
grep_args = "--files-with-matches"
@ -239,11 +252,12 @@ module OS
private
CELLAR_RX = %r{\A#{HOMEBREW_CELLAR}/(?<formula_name>[^/]+)/[^/]+}
CELLAR_RX = T.let(%r{\A#{HOMEBREW_CELLAR}/(?<formula_name>[^/]+)/[^/]+}, Regexp)
private_constant :CELLAR_RX
# Replace HOMEBREW_CELLAR references with HOMEBREW_PREFIX/opt references
# if the Cellar reference is to a different keg.
sig { params(filename: String).returns(String) }
def opt_name_for(filename)
return filename unless filename.start_with?(HOMEBREW_PREFIX.to_s)
return filename if filename.start_with?(path.to_s)
@ -252,6 +266,7 @@ module OS
filename.sub(CELLAR_RX, "#{HOMEBREW_PREFIX}/opt/#{matches[:formula_name]}")
end
sig { params(filename: String).returns(T::Boolean) }
def rooted_in_build_directory?(filename)
# CMake normalises `/private/tmp` to `/tmp`.
# https://gitlab.kitware.com/cmake/cmake/-/issues/23251

View File

@ -2024,7 +2024,7 @@ class Formula
raise "No universal binaries found to deuniversalize" if targets.blank?
targets&.each do |target|
targets.compact.each do |target|
extract_macho_slice_from(Pathname(target), Hardware::CPU.arch)
end
end

View File

@ -234,7 +234,7 @@ module FormulaCellarChecks
keg = Keg.new(prefix)
matches = []
keg.each_unique_file_matching(HOMEBREW_SHIMS_PATH) do |f|
keg.each_unique_file_matching(HOMEBREW_SHIMS_PATH.to_s) do |f|
match = f.relative_path_from(keg.to_path)
next if match.to_s.match? %r{^share/doc/.+?/INFO_BIN$}

View File

@ -1591,7 +1591,7 @@ on_request: installed_on_request?, options:)
keg = Keg.new(formula.prefix)
skip_linkage = formula.bottle_specification.skip_relocation?
keg.replace_placeholders_with_locations(tab.changed_files, skip_linkage:)
keg.replace_placeholders_with_locations(tab.changed_files, skip_linkage:) if tab.changed_files
cellar = formula.bottle_specification.tag_to_cellar(Utils::Bottles.tag)
return if [:any, :any_skip_relocation].include?(cellar)

View File

@ -1,23 +1,25 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
class Keg
PREFIX_PLACEHOLDER = "@@HOMEBREW_PREFIX@@"
CELLAR_PLACEHOLDER = "@@HOMEBREW_CELLAR@@"
REPOSITORY_PLACEHOLDER = "@@HOMEBREW_REPOSITORY@@"
LIBRARY_PLACEHOLDER = "@@HOMEBREW_LIBRARY@@"
PERL_PLACEHOLDER = "@@HOMEBREW_PERL@@"
JAVA_PLACEHOLDER = "@@HOMEBREW_JAVA@@"
NULL_BYTE = "\x00"
NULL_BYTE_STRING = "\\x00"
PREFIX_PLACEHOLDER = T.let("@@HOMEBREW_PREFIX@@", String)
CELLAR_PLACEHOLDER = T.let("@@HOMEBREW_CELLAR@@", String)
REPOSITORY_PLACEHOLDER = T.let("@@HOMEBREW_REPOSITORY@@", String)
LIBRARY_PLACEHOLDER = T.let("@@HOMEBREW_LIBRARY@@", String)
PERL_PLACEHOLDER = T.let("@@HOMEBREW_PERL@@", String)
JAVA_PLACEHOLDER = T.let("@@HOMEBREW_JAVA@@", String)
NULL_BYTE = T.let("\x00", String)
NULL_BYTE_STRING = T.let("\\x00", String)
class Relocation
RELOCATABLE_PATH_REGEX_PREFIX = /(?:(?<=-F|-I|-L|-isystem)|(?<![a-zA-Z0-9]))/
RELOCATABLE_PATH_REGEX_PREFIX = T.let(/(?:(?<=-F|-I|-L|-isystem)|(?<![a-zA-Z0-9]))/, Regexp)
sig { void }
def initialize
@replacement_map = T.let({}, T::Hash[Symbol, [T.any(String, Regexp), String]])
end
sig { returns(Relocation) }
def freeze
@replacement_map.freeze
super
@ -62,6 +64,7 @@ class Keg
end
end
sig { void }
def fix_dynamic_linkage
symlink_files.each do |file|
link = file.readlink
@ -78,9 +81,8 @@ class Keg
end
end
def relocate_dynamic_linkage(_relocation, skip_protodesc_cold: false)
[]
end
sig { params(_relocation: Relocation, skip_protodesc_cold: T::Boolean).void }
def relocate_dynamic_linkage(_relocation, skip_protodesc_cold: false); end
JAVA_REGEX = %r{#{HOMEBREW_PREFIX}/opt/openjdk(@\d+(\.\d+)*)?/libexec(/openjdk\.jdk/Contents/Home)?}
@ -102,6 +104,7 @@ class Keg
}
end
sig { returns(Relocation) }
def prepare_relocation_to_placeholders
relocation = Relocation.new
@ -130,12 +133,14 @@ class Keg
relocation
end
sig { returns(T::Array[Pathname]) }
def replace_locations_with_placeholders
relocation = prepare_relocation_to_placeholders.freeze
relocate_dynamic_linkage(relocation, skip_protodesc_cold: true)
replace_text_in_files(relocation)
end
sig { returns(Relocation) }
def prepare_relocation_to_locations
relocation = Relocation.new
relocation.add_replacement_pair(:prefix, PREFIX_PLACEHOLDER, HOMEBREW_PREFIX.to_s)
@ -150,12 +155,14 @@ class Keg
relocation
end
sig { params(files: T::Array[Pathname], skip_linkage: T::Boolean).void }
def replace_placeholders_with_locations(files, skip_linkage: false)
relocation = prepare_relocation_to_locations.freeze
relocate_dynamic_linkage(relocation) unless skip_linkage
replace_text_in_files(relocation, files:)
end
sig { returns(T.nilable(String)) }
def openjdk_dep_name_if_applicable
deps = runtime_dependencies
return if deps.blank?
@ -164,10 +171,11 @@ class Keg
dep_names.find { |d| d.match? Version.formula_optionally_versioned_regex(:openjdk) }
end
sig { params(relocation: Relocation, files: T.nilable(T::Array[Pathname])).returns(T::Array[Pathname]) }
def replace_text_in_files(relocation, files: nil)
files ||= text_files | libtool_files
changed_files = T.let([], Array)
changed_files = T.let([], T::Array[Pathname])
files.map { path.join(_1) }.group_by { |f| f.stat.ino }.each_value do |first, *rest|
s = first.open("rb", &:read)
@ -188,6 +196,7 @@ class Keg
changed_files
end
sig { params(keg: Keg, old_prefix: T.any(String, Pathname), new_prefix: T.any(String, Pathname)).void }
def relocate_build_prefix(keg, old_prefix, new_prefix)
each_unique_file_matching(old_prefix) do |file|
# Skip files which are not binary, as they do not need null padding.
@ -202,12 +211,12 @@ class Keg
binary = File.binread file
odebug "Replacing build prefix in: #{file}"
binary_strings = binary.split(/#{NULL_BYTE}/o, -1)
match_indices = binary_strings.each_index.select { |i| binary_strings.fetch(i).include?(old_prefix) }
match_indices = binary_strings.each_index.select { |i| binary_strings.fetch(i).include?(old_prefix.to_s) }
# Only perform substitution on strings which match prefix regex.
match_indices.each do |i|
s = binary_strings.fetch(i)
binary_strings[i] = s.gsub(old_prefix, new_prefix)
binary_strings[i] = s.gsub(old_prefix.to_s, new_prefix.to_s)
.ljust(s.size, NULL_BYTE)
end
@ -227,15 +236,18 @@ class Keg
end
end
sig { params(_options: T::Hash[Symbol, T::Boolean]).returns(T::Array[Symbol]) }
def detect_cxx_stdlibs(_options = {})
[]
end
sig { returns(String) }
def recursive_fgrep_args
# for GNU grep; overridden for BSD grep on OS X
"-lr"
end
sig { returns([String, T::Array[String]]) }
def egrep_args
grep_bin = "grep"
grep_args = [
@ -247,7 +259,8 @@ class Keg
[grep_bin, grep_args]
end
def each_unique_file_matching(string)
sig { params(string: T.any(String, Pathname), _block: T.proc.params(arg0: Pathname).void).void }
def each_unique_file_matching(string, &_block)
Utils.popen_read("fgrep", recursive_fgrep_args, string, to_s) do |io|
hardlinks = Set.new
@ -263,6 +276,7 @@ class Keg
end
end
sig { params(file: Pathname).returns(T::Boolean) }
def binary_file?(file)
grep_bin, grep_args = egrep_args
@ -272,14 +286,17 @@ class Keg
Utils.popen_read(grep_bin, *grep_args, NULL_BYTE_STRING, file).present?
end
sig { returns(Pathname) }
def lib
path/"lib"
end
sig { returns(Pathname) }
def libexec
path/"libexec"
end
sig { returns(T::Array[Pathname]) }
def text_files
text_files = []
return text_files if !which("file") || !which("xargs")
@ -327,6 +344,7 @@ class Keg
text_files
end
sig { returns(T::Array[Pathname]) }
def libtool_files
libtool_files = []
@ -338,6 +356,7 @@ class Keg
libtool_files
end
sig { returns(T::Array[Pathname]) }
def symlink_files
symlink_files = []
path.find do |pn|
@ -347,6 +366,10 @@ class Keg
symlink_files
end
sig {
params(file: Pathname, string: String, ignores: T::Array[Regexp], linked_libraries: T::Array[Pathname],
formula_and_runtime_deps_names: T.nilable(T::Array[String])).returns(T::Array[[String, String]])
}
def self.text_matches_in_file(file, string, ignores, linked_libraries, formula_and_runtime_deps_names)
text_matches = []
path_regex = Relocation.path_to_regex(string)
@ -386,6 +409,7 @@ class Keg
text_matches
end
sig { params(_file: Pathname, _string: String).returns(T::Array[Pathname]) }
def self.file_linked_libraries(_file, _string)
[]
end