keg_relocate: port to generic OS. (#453)
This commit is contained in:
parent
df7e36b86c
commit
f1c4141885
@ -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 }
|
||||
result ||= linked_libraries.any?
|
||||
else
|
||||
linked_libraries = []
|
||||
end
|
||||
linked_libraries = Keg.file_linked_libraries(file, string)
|
||||
result ||= linked_libraries.any?
|
||||
|
||||
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
|
||||
|
||||
5
Library/Homebrew/extend/os/keg_relocate.rb
Normal file
5
Library/Homebrew/extend/os/keg_relocate.rb
Normal file
@ -0,0 +1,5 @@
|
||||
require "keg_relocate"
|
||||
|
||||
if OS.mac?
|
||||
require "extend/os/mac/keg_relocate"
|
||||
end
|
||||
135
Library/Homebrew/extend/os/mac/keg_relocate.rb
Normal file
135
Library/Homebrew/extend/os/mac/keg_relocate.rb
Normal 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
|
||||
@ -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,
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user