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 }
 | 
			
		||||
      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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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