keg_relocate: fix relocation of frameworks

When fixing references to regular dylibs, it is sufficient to search for
a file with the same base name, e.g., `libpoppler.56.dylib`. However, if
the broken reference is to a framework, we also have to take into
account preceding path components to find a suitable match. Framework
references (according to the `dyld` man page) come in two flavors:

- `XXX.framework/Versions/YYY/XXX` (with version)
- `XXX.framework/XXX` (without version)

The change here is to detect these patterns and to make sure that the
fixed library reference has the same suffix as the broken one.

Prior to this fix, a broken framework reference (if originating in a
sister framework) to `QtXml.framework/Versions/5/QtXml` would have been
rewritten to `<qt5-keg>/lib/QtXml.framework/QtXml`. In practice, this
mostly works, but is technically incorrect and thus creates problems
like in Homebrew/homebrew#42191. With this fix, the framework reference is correctly
rewritten to `<qt5-keg>/lib/QtXml.framework/Versions/5/QtXml`.

Closes Homebrew/homebrew#45494.

Signed-off-by: Mike McQuaid <mike@mikemcquaid.com>
This commit is contained in:
Martin Afanasjew 2015-10-29 23:57:30 +01:00 committed by Mike McQuaid
parent 55063f0ec7
commit 06634edab7

View File

@ -2,6 +2,10 @@ class Keg
PREFIX_PLACEHOLDER = "@@HOMEBREW_PREFIX@@".freeze PREFIX_PLACEHOLDER = "@@HOMEBREW_PREFIX@@".freeze
CELLAR_PLACEHOLDER = "@@HOMEBREW_CELLAR@@".freeze CELLAR_PLACEHOLDER = "@@HOMEBREW_CELLAR@@".freeze
# Matches framework references like `XXX.framework/Versions/YYY/XXX` and
# `XXX.framework/XXX`, both with or without a slash-delimited prefix.
FRAMEWORK_RX = %r{(?:^|/)(?<suffix>(?<name>[^/]+)\.framework/(?:Versions/[^/]+/)?\k<name>)$}.freeze
def fix_install_names(options = {}) def fix_install_names(options = {})
mach_o_files.each do |file| mach_o_files.each do |file|
file.ensure_writable do file.ensure_writable do
@ -130,7 +134,7 @@ class Keg
"@loader_path/#{bad_name}" "@loader_path/#{bad_name}"
elsif file.mach_o_executable? && (lib + bad_name).exist? elsif file.mach_o_executable? && (lib + bad_name).exist?
"#{lib}/#{bad_name}" "#{lib}/#{bad_name}"
elsif (abs_name = find_dylib(Pathname.new(bad_name).basename)) && abs_name.exist? elsif (abs_name = find_dylib(bad_name)) && abs_name.exist?
abs_name.to_s abs_name.to_s
else else
opoo "Could not fix #{bad_name} in #{file}" opoo "Could not fix #{bad_name} in #{file}"
@ -156,8 +160,18 @@ class Keg
opt_record.join(relative_dirname, basename).to_s opt_record.join(relative_dirname, basename).to_s
end end
def find_dylib(name) def find_dylib_suffix_from(bad_name)
lib.find { |pn| break pn if pn.basename == name } if lib.directory? if (framework = bad_name.match(FRAMEWORK_RX))
framework["suffix"]
else
File.basename(bad_name)
end
end
def find_dylib(bad_name)
return unless lib.directory?
suffix = "/#{find_dylib_suffix_from(bad_name)}"
lib.find { |pn| break pn if pn.to_s.end_with?(suffix) }
end end
def mach_o_files def mach_o_files