diff --git a/Library/Homebrew/cmd/bottle.rb b/Library/Homebrew/cmd/bottle.rb index 1975d7ccfe39289e536da2cd928d664b5e4b992a..50ce9470932ab935dd164caebe05e2379dac5ec9 100644 --- a/Library/Homebrew/cmd/bottle.rb +++ b/Library/Homebrew/cmd/bottle.rb @@ -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 diff --git a/Library/Homebrew/extend/os/keg_relocate.rb b/Library/Homebrew/extend/os/keg_relocate.rb new file mode 100644 index 0000000000000000000000000000000000000000..2fde45d93a5b881418c0dbf421fa5277ddf60b90 --- /dev/null +++ b/Library/Homebrew/extend/os/keg_relocate.rb @@ -0,0 +1,5 @@ +require "keg_relocate" + +if OS.mac? + require "extend/os/mac/keg_relocate" +end diff --git a/Library/Homebrew/extend/os/mac/keg_relocate.rb b/Library/Homebrew/extend/os/mac/keg_relocate.rb new file mode 100644 index 0000000000000000000000000000000000000000..80f6875168ef7ddb3fcbc109fdbd909b65cf43f6 --- /dev/null +++ b/Library/Homebrew/extend/os/mac/keg_relocate.rb @@ -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 diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb index e44c78087a5851492f7264cac66a2a41e67b3e72..375a529bbd9321b7d334e65beaeefb84bdd1e388 100644 --- a/Library/Homebrew/formula_installer.rb +++ b/Library/Homebrew/formula_installer.rb @@ -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, diff --git a/Library/Homebrew/keg_relocate.rb b/Library/Homebrew/keg_relocate.rb index be41841f1fe754667e1d67af22f1e61f58b42c65..31732016896f3f1147433b2c22bcf7d157ef5047 100644 --- a/Library/Homebrew/keg_relocate.rb +++ b/Library/Homebrew/keg_relocate.rb @@ -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"