keg_relocate: port to generic OS. (#453)

This commit is contained in:
Mike McQuaid 2016-07-09 13:52:05 +01:00 committed by GitHub
parent df7e36b86c
commit f1c4141885
5 changed files with 161 additions and 133 deletions

View File

@ -57,15 +57,8 @@ module Homebrew
# skip document file.
next if Metafiles::EXTENSIONS.include? file.extname
# Check dynamic library linkage. Importantly, do not run otool on static
# libraries, which will falsely report "linkage" to themselves.
if file.mach_o_executable? || file.dylib? || file.mach_o_bundle?
linked_libraries = file.dynamically_linked_libraries
linked_libraries = linked_libraries.select { |lib| lib.include? string }
linked_libraries = Keg.file_linked_libraries(file, string)
result ||= linked_libraries.any?
else
linked_libraries = []
end
if ARGV.verbose?
print_filename(string, file) if linked_libraries.any?
@ -195,9 +188,9 @@ module Homebrew
begin
unless ARGV.include? "--skip-relocation"
keg.relocate_install_names prefix, Keg::PREFIX_PLACEHOLDER,
keg.relocate_dynamic_linkage prefix, Keg::PREFIX_PLACEHOLDER,
cellar, Keg::CELLAR_PLACEHOLDER
keg.relocate_text_files prefix, Keg::PREFIX_PLACEHOLDER,
keg.relocate_dynamic_files prefix, Keg::PREFIX_PLACEHOLDER,
cellar, Keg::CELLAR_PLACEHOLDER
end
@ -264,7 +257,7 @@ module Homebrew
ignore_interrupts do
original_tab.write if original_tab
unless ARGV.include? "--skip-relocation"
keg.relocate_install_names Keg::PREFIX_PLACEHOLDER, prefix,
keg.relocate_dynamic_linkage Keg::PREFIX_PLACEHOLDER, prefix,
Keg::CELLAR_PLACEHOLDER, cellar
keg.relocate_text_files Keg::PREFIX_PLACEHOLDER, prefix,
Keg::CELLAR_PLACEHOLDER, cellar

View File

@ -0,0 +1,5 @@
require "keg_relocate"
if OS.mac?
require "extend/os/mac/keg_relocate"
end

View File

@ -0,0 +1,135 @@
class Keg
def fix_dynamic_linkage
mach_o_files.each do |file|
file.ensure_writable do
change_dylib_id(dylib_id_for(file), file) if file.dylib?
each_install_name_for(file) do |bad_name|
# Don't fix absolute paths unless they are rooted in the build directory
next if bad_name.start_with?("/") && !bad_name.start_with?(HOMEBREW_TEMP.to_s)
new_name = fixed_name(file, bad_name)
change_install_name(bad_name, new_name, file) unless new_name == bad_name
end
end
end
generic_fix_dynamic_linkage
end
def relocate_dynamic_linkage(old_prefix, new_prefix, old_cellar, new_cellar)
mach_o_files.each do |file|
file.ensure_writable do
if file.dylib?
id = dylib_id_for(file).sub(old_prefix, new_prefix)
change_dylib_id(id, file)
end
each_install_name_for(file) do |old_name|
if old_name.start_with? old_cellar
new_name = old_name.sub(old_cellar, new_cellar)
elsif old_name.start_with? old_prefix
new_name = old_name.sub(old_prefix, new_prefix)
end
change_install_name(old_name, new_name, file) if new_name
end
end
end
end
# Detects the C++ dynamic libraries in place, scanning the dynamic links
# of the files within the keg.
# Note that this doesn't attempt to distinguish between libstdc++ versions,
# for instance between Apple libstdc++ and GNU libstdc++
def detect_cxx_stdlibs(options = {})
skip_executables = options.fetch(:skip_executables, false)
results = Set.new
mach_o_files.each do |file|
next if file.mach_o_executable? && skip_executables
dylibs = file.dynamically_linked_libraries
results << :libcxx unless dylibs.grep(/libc\+\+.+\.dylib/).empty?
results << :libstdcxx unless dylibs.grep(/libstdc\+\+.+\.dylib/).empty?
end
results.to_a
end
# 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.
def fixed_name(file, bad_name)
if bad_name.start_with? PREFIX_PLACEHOLDER
bad_name.sub(PREFIX_PLACEHOLDER, HOMEBREW_PREFIX.to_s)
elsif bad_name.start_with? CELLAR_PLACEHOLDER
bad_name.sub(CELLAR_PLACEHOLDER, HOMEBREW_CELLAR.to_s)
elsif (file.dylib? || file.mach_o_bundle?) && (file.parent + bad_name).exist?
"@loader_path/#{bad_name}"
elsif file.mach_o_executable? && (lib + bad_name).exist?
"#{lib}/#{bad_name}"
elsif (abs_name = find_dylib(bad_name)) && abs_name.exist?
abs_name.to_s
else
opoo "Could not fix #{bad_name} in #{file}"
bad_name
end
end
def each_install_name_for(file, &block)
dylibs = file.dynamically_linked_libraries
dylibs.reject! { |fn| fn =~ /^@(loader_|executable_|r)path/ }
dylibs.each(&block)
end
def dylib_id_for(file)
# The new dylib ID should have the same basename as the old dylib ID, not
# the basename of the file itself.
basename = File.basename(file.dylib_id)
relative_dirname = file.dirname.relative_path_from(path)
opt_record.join(relative_dirname, basename).to_s
end
# Matches framework references like `XXX.framework/Versions/YYY/XXX` and
# `XXX.framework/XXX`, both with or without a slash-delimited prefix.
FRAMEWORK_RX = %r{(?:^|/)(([^/]+)\.framework/(?:Versions/[^/]+/)?\2)$}
def find_dylib_suffix_from(bad_name)
if (framework = bad_name.match(FRAMEWORK_RX))
framework[1]
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
def mach_o_files
hardlinks = Set.new
mach_o_files = []
path.find do |pn|
next if pn.symlink? || pn.directory?
next unless pn.dylib? || pn.mach_o_bundle? || pn.mach_o_executable?
# if we've already processed a file, ignore its hardlinks (which have the same dev ID and inode)
# this prevents relocations from being performed on a binary more than once
next unless hardlinks.add? [pn.stat.dev, pn.stat.ino]
mach_o_files << pn
end
mach_o_files
end
def self.file_linked_libraries(file, string)
# Check dynamic library linkage. Importantly, do not run otool on static
# libraries, which will falsely report "linkage" to themselves.
if file.mach_o_executable? || file.dylib? || file.mach_o_bund
file.dynamically_linked_libraries.select { |lib| lib.include? string }
else
[]
end
end
end

View File

@ -470,7 +470,7 @@ class FormulaInstaller
link(keg)
unless @poured_bottle && formula.bottle_specification.skip_relocation?
fix_install_names(keg)
fix_dynamic_linkage(keg)
end
if formula.post_install_defined?
@ -687,10 +687,10 @@ class FormulaInstaller
Homebrew.failed = true
end
def fix_install_names(keg)
keg.fix_install_names
def fix_dynamic_linkage(keg)
keg.fix_dynamic_linkage
rescue Exception => e
onoe "Failed to fix install names"
onoe "Failed to fix install linkage"
puts "The formula built, but you may encounter issues using it or linking other"
puts "formula against it."
ohai e, e.backtrace if debug?
@ -736,7 +736,7 @@ class FormulaInstaller
keg = Keg.new(formula.prefix)
unless formula.bottle_specification.skip_relocation?
keg.relocate_install_names Keg::PREFIX_PLACEHOLDER, HOMEBREW_PREFIX.to_s,
keg.relocate_dynamic_linkage Keg::PREFIX_PLACEHOLDER, HOMEBREW_PREFIX.to_s,
Keg::CELLAR_PLACEHOLDER, HOMEBREW_CELLAR.to_s
end
keg.relocate_text_files Keg::PREFIX_PLACEHOLDER, HOMEBREW_PREFIX.to_s,

View File

@ -2,21 +2,7 @@ class Keg
PREFIX_PLACEHOLDER = "@@HOMEBREW_PREFIX@@".freeze
CELLAR_PLACEHOLDER = "@@HOMEBREW_CELLAR@@".freeze
def fix_install_names
mach_o_files.each do |file|
file.ensure_writable do
change_dylib_id(dylib_id_for(file), file) if file.dylib?
each_install_name_for(file) do |bad_name|
# Don't fix absolute paths unless they are rooted in the build directory
next if bad_name.start_with?("/") && !bad_name.start_with?(HOMEBREW_TEMP.to_s)
new_name = fixed_name(file, bad_name)
change_install_name(bad_name, new_name, file) unless new_name == bad_name
end
end
end
def fix_dynamic_linkage
symlink_files.each do |file|
link = file.readlink
# Don't fix relative symlinks
@ -26,26 +12,10 @@ class Keg
end
end
end
alias generic_fix_dynamic_linkage fix_dynamic_linkage
def relocate_install_names(old_prefix, new_prefix, old_cellar, new_cellar)
mach_o_files.each do |file|
file.ensure_writable do
if file.dylib?
id = dylib_id_for(file).sub(old_prefix, new_prefix)
change_dylib_id(id, file)
end
each_install_name_for(file) do |old_name|
if old_name.start_with? old_cellar
new_name = old_name.sub(old_cellar, new_cellar)
elsif old_name.start_with? old_prefix
new_name = old_name.sub(old_prefix, new_prefix)
end
change_install_name(old_name, new_name, file) if new_name
end
end
end
def relocate_dynamic_linkage(old_prefix, new_prefix, old_cellar, new_cellar)
[]
end
def relocate_text_files(old_prefix, new_prefix, old_cellar, new_cellar)
@ -70,22 +40,8 @@ class Keg
end
end
# Detects the C++ dynamic libraries in place, scanning the dynamic links
# of the files within the keg.
# Note that this doesn't attempt to distinguish between libstdc++ versions,
# for instance between Apple libstdc++ and GNU libstdc++
def detect_cxx_stdlibs(options = {})
skip_executables = options.fetch(:skip_executables, false)
results = Set.new
mach_o_files.each do |file|
next if file.mach_o_executable? && skip_executables
dylibs = file.dynamically_linked_libraries
results << :libcxx unless dylibs.grep(/libc\+\+.+\.dylib/).empty?
results << :libstdcxx unless dylibs.grep(/libstdc\+\+.+\.dylib/).empty?
end
results.to_a
[]
end
def each_unique_file_matching(string)
@ -100,77 +56,10 @@ class Keg
end
end
# 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.
def fixed_name(file, bad_name)
if bad_name.start_with? PREFIX_PLACEHOLDER
bad_name.sub(PREFIX_PLACEHOLDER, HOMEBREW_PREFIX.to_s)
elsif bad_name.start_with? CELLAR_PLACEHOLDER
bad_name.sub(CELLAR_PLACEHOLDER, HOMEBREW_CELLAR.to_s)
elsif (file.dylib? || file.mach_o_bundle?) && (file.parent + bad_name).exist?
"@loader_path/#{bad_name}"
elsif file.mach_o_executable? && (lib + bad_name).exist?
"#{lib}/#{bad_name}"
elsif (abs_name = find_dylib(bad_name)) && abs_name.exist?
abs_name.to_s
else
opoo "Could not fix #{bad_name} in #{file}"
bad_name
end
end
def lib
path.join("lib")
end
def each_install_name_for(file, &block)
dylibs = file.dynamically_linked_libraries
dylibs.reject! { |fn| fn =~ /^@(loader_|executable_|r)path/ }
dylibs.each(&block)
end
def dylib_id_for(file)
# The new dylib ID should have the same basename as the old dylib ID, not
# the basename of the file itself.
basename = File.basename(file.dylib_id)
relative_dirname = file.dirname.relative_path_from(path)
opt_record.join(relative_dirname, basename).to_s
end
# Matches framework references like `XXX.framework/Versions/YYY/XXX` and
# `XXX.framework/XXX`, both with or without a slash-delimited prefix.
FRAMEWORK_RX = %r{(?:^|/)(([^/]+)\.framework/(?:Versions/[^/]+/)?\2)$}.freeze
def find_dylib_suffix_from(bad_name)
if (framework = bad_name.match(FRAMEWORK_RX))
framework[1]
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
def mach_o_files
hardlinks = Set.new
mach_o_files = []
path.find do |pn|
next if pn.symlink? || pn.directory?
next unless pn.dylib? || pn.mach_o_bundle? || pn.mach_o_executable?
# if we've already processed a file, ignore its hardlinks (which have the same dev ID and inode)
# this prevents relocations from being performed on a binary more than once
next unless hardlinks.add? [pn.stat.dev, pn.stat.ino]
mach_o_files << pn
end
mach_o_files
end
def text_files
text_files = []
path.find do |pn|
@ -203,4 +92,10 @@ class Keg
symlink_files
end
def self.file_linked_libraries(file, string)
[]
end
end
require "extend/os/keg_relocate"