Relocate bottles on Linux using patchelf
Ensure patchelf is installed to pour bottles and build bottles.
This commit is contained in:
		
							parent
							
								
									4ffc8b137b
								
							
						
					
					
						commit
						1b688a3a25
					
				@ -87,11 +87,20 @@ module Homebrew
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    return merge if args.merge?
 | 
			
		||||
    ensure_relocation_formulae_installed!
 | 
			
		||||
    ARGV.resolved_formulae.each do |f|
 | 
			
		||||
      bottle_formula f
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def ensure_relocation_formulae_installed!
 | 
			
		||||
    Keg.relocation_formulae.each do |f|
 | 
			
		||||
      next if Formula[f].installed?
 | 
			
		||||
      ohai "Installing #{f}..."
 | 
			
		||||
      safe_system HOMEBREW_BREW_FILE, "install", f
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def keg_contain?(string, keg, ignores)
 | 
			
		||||
    @put_string_exists_header, @put_filenames = nil
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1 +1,5 @@
 | 
			
		||||
require "extend/os/mac/keg_relocate" if OS.mac?
 | 
			
		||||
if OS.mac?
 | 
			
		||||
  require "extend/os/mac/keg_relocate"
 | 
			
		||||
elsif OS.linux?
 | 
			
		||||
  require "extend/os/linux/keg_relocate"
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										81
									
								
								Library/Homebrew/extend/os/linux/keg_relocate.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								Library/Homebrew/extend/os/linux/keg_relocate.rb
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
			
		||||
class Keg
 | 
			
		||||
  def relocate_dynamic_linkage(relocation)
 | 
			
		||||
    # Patching patchelf using itself fails with "Text file busy" or SIGBUS.
 | 
			
		||||
    return if name == "patchelf"
 | 
			
		||||
 | 
			
		||||
    elf_files.each do |file|
 | 
			
		||||
      file.ensure_writable do
 | 
			
		||||
        change_rpath(file, relocation.old_prefix, relocation.new_prefix)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def change_rpath(file, old_prefix, new_prefix)
 | 
			
		||||
    return if !file.elf? || !file.dynamic_elf?
 | 
			
		||||
 | 
			
		||||
    patchelf = DevelopmentTools.locate "patchelf"
 | 
			
		||||
    cmd_rpath = [patchelf, "--print-rpath", file]
 | 
			
		||||
    old_rpath = Utils.popen_read(*cmd_rpath, err: :out).strip
 | 
			
		||||
 | 
			
		||||
    # patchelf requires that the ELF file have a .dynstr section.
 | 
			
		||||
    # Skip ELF files that do not have a .dynstr section.
 | 
			
		||||
    return if ["cannot find section .dynstr", "strange: no string table"].include?(old_rpath)
 | 
			
		||||
    raise ErrorDuringExecution, "#{cmd_rpath}\n#{old_rpath}" unless $CHILD_STATUS.success?
 | 
			
		||||
 | 
			
		||||
    rpath = old_rpath
 | 
			
		||||
            .split(":")
 | 
			
		||||
            .map { |x| x.sub(old_prefix, new_prefix) }
 | 
			
		||||
            .select { |x| x.start_with?(new_prefix, "$ORIGIN") }
 | 
			
		||||
 | 
			
		||||
    lib_path = "#{new_prefix}/lib"
 | 
			
		||||
    rpath << lib_path unless rpath.include? lib_path
 | 
			
		||||
    new_rpath = rpath.join(":")
 | 
			
		||||
    cmd = [patchelf, "--force-rpath", "--set-rpath", new_rpath]
 | 
			
		||||
 | 
			
		||||
    if file.binary_executable?
 | 
			
		||||
      old_interpreter = Utils.safe_popen_read(patchelf, "--print-interpreter", file).strip
 | 
			
		||||
      new_interpreter = if File.readable? "#{new_prefix}/lib/ld.so"
 | 
			
		||||
        "#{new_prefix}/lib/ld.so"
 | 
			
		||||
      else
 | 
			
		||||
        old_interpreter.sub old_prefix, new_prefix
 | 
			
		||||
      end
 | 
			
		||||
      cmd << "--set-interpreter" << new_interpreter if old_interpreter != new_interpreter
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    return if old_rpath == new_rpath && old_interpreter == new_interpreter
 | 
			
		||||
    safe_system(*cmd, file)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def detect_cxx_stdlibs(options = {})
 | 
			
		||||
    skip_executables = options.fetch(:skip_executables, false)
 | 
			
		||||
    results = Set.new
 | 
			
		||||
    elf_files.each do |file|
 | 
			
		||||
      next unless file.dynamic_elf?
 | 
			
		||||
      next if file.binary_executable? && skip_executables
 | 
			
		||||
      dylibs = file.dynamically_linked_libraries
 | 
			
		||||
      results << :libcxx if dylibs.any? { |s| s.include? "libc++.so" }
 | 
			
		||||
      results << :libstdcxx if dylibs.any? { |s| s.include? "libstdc++.so" }
 | 
			
		||||
    end
 | 
			
		||||
    results.to_a
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def elf_files
 | 
			
		||||
    hardlinks = Set.new
 | 
			
		||||
    elf_files = []
 | 
			
		||||
    path.find do |pn|
 | 
			
		||||
      next if pn.symlink? || pn.directory?
 | 
			
		||||
      next if !pn.dylib? && !pn.binary_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]
 | 
			
		||||
      elf_files << pn
 | 
			
		||||
    end
 | 
			
		||||
    elf_files
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def self.relocation_formulae
 | 
			
		||||
    ["patchelf"]
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -473,6 +473,7 @@ class FormulaInstaller
 | 
			
		||||
 | 
			
		||||
  def expand_dependencies(deps)
 | 
			
		||||
    inherited_options = Hash.new { |hash, key| hash[key] = Options.new }
 | 
			
		||||
    pour_bottle = pour_bottle?
 | 
			
		||||
 | 
			
		||||
    expanded_deps = Dependency.expand(formula, deps) do |dependent, dep|
 | 
			
		||||
      inherited_options[dep.name] |= inherited_options_for(dep)
 | 
			
		||||
@ -480,6 +481,7 @@ class FormulaInstaller
 | 
			
		||||
        dependent,
 | 
			
		||||
        inherited_options.fetch(dependent.name, []),
 | 
			
		||||
      )
 | 
			
		||||
      pour_bottle = true if install_bottle_for?(dep.to_formula, build)
 | 
			
		||||
 | 
			
		||||
      if dep.prune_from_option?(build)
 | 
			
		||||
        Dependency.prune
 | 
			
		||||
@ -494,6 +496,16 @@ class FormulaInstaller
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    if pour_bottle
 | 
			
		||||
      bottle_deps = Keg.relocation_formulae
 | 
			
		||||
                       .map { |formula| Dependency.new(formula) }
 | 
			
		||||
                       .reject do |dep|
 | 
			
		||||
        inherited_options[dep.name] |= inherited_options_for(dep)
 | 
			
		||||
        dep.satisfied? inherited_options[dep.name]
 | 
			
		||||
      end
 | 
			
		||||
      expanded_deps = Dependency.merge_repeats(bottle_deps + expanded_deps) unless bottle_deps.empty?
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    expanded_deps.map { |dep| [dep, inherited_options[dep.name]] }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -175,6 +175,10 @@ class Keg
 | 
			
		||||
  def self.file_linked_libraries(_file, _string)
 | 
			
		||||
    []
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def self.relocation_formulae
 | 
			
		||||
    []
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
require "extend/os/keg_relocate"
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,9 @@ describe "brew bottle", :integration_test do
 | 
			
		||||
      expect { brew "install", "--build-bottle", testball }
 | 
			
		||||
        .to be_a_success
 | 
			
		||||
 | 
			
		||||
      setup_test_formula "patchelf"
 | 
			
		||||
      (HOMEBREW_CELLAR/"patchelf/1.0/bin").mkpath
 | 
			
		||||
 | 
			
		||||
      expect { brew "bottle", "--no-rebuild", testball }
 | 
			
		||||
        .to output(/Formula not from core or any taps/).to_stderr
 | 
			
		||||
        .and not_to_output.to_stdout
 | 
			
		||||
 | 
			
		||||
@ -17,6 +17,9 @@ describe FormulaInstaller do
 | 
			
		||||
    expect(formula).to be_bottled
 | 
			
		||||
    expect(formula).to pour_bottle
 | 
			
		||||
 | 
			
		||||
    stub_formula_loader formula
 | 
			
		||||
    stub_formula_loader formula("patchelf") { url "patchelf-1.0" }
 | 
			
		||||
    allow(Formula["patchelf"]).to receive(:installed?).and_return(true)
 | 
			
		||||
    described_class.new(formula).install
 | 
			
		||||
 | 
			
		||||
    keg = Keg.new(formula.prefix)
 | 
			
		||||
 | 
			
		||||
@ -136,20 +136,26 @@ describe Formulary do
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context "with installed Formula" do
 | 
			
		||||
      let(:formula) { described_class.factory(formula_path) }
 | 
			
		||||
      let(:installer) { FormulaInstaller.new(formula) }
 | 
			
		||||
      before do
 | 
			
		||||
        allow(Formulary).to receive(:loader_for).and_call_original
 | 
			
		||||
        stub_formula_loader formula("patchelf") { url "patchelf-1.0" }
 | 
			
		||||
        allow(Formula["patchelf"]).to receive(:installed?).and_return(true)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      let(:installed_formula) { described_class.factory(formula_path) }
 | 
			
		||||
      let(:installer) { FormulaInstaller.new(installed_formula) }
 | 
			
		||||
 | 
			
		||||
      it "returns a Formula when given a rack" do
 | 
			
		||||
        installer.install
 | 
			
		||||
 | 
			
		||||
        f = described_class.from_rack(formula.rack)
 | 
			
		||||
        f = described_class.from_rack(installed_formula.rack)
 | 
			
		||||
        expect(f).to be_kind_of(Formula)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "returns a Formula when given a Keg" do
 | 
			
		||||
        installer.install
 | 
			
		||||
 | 
			
		||||
        keg = Keg.new(formula.prefix)
 | 
			
		||||
        keg = Keg.new(installed_formula.prefix)
 | 
			
		||||
        f = described_class.from_keg(keg)
 | 
			
		||||
        expect(f).to be_kind_of(Formula)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
@ -157,6 +157,10 @@ RSpec.shared_context "integration test" do
 | 
			
		||||
        url "https://example.com/#{name}-1.0"
 | 
			
		||||
        depends_on "foo"
 | 
			
		||||
      RUBY
 | 
			
		||||
    when "patchelf"
 | 
			
		||||
      content = <<~RUBY
 | 
			
		||||
        url "https://example.com/#{name}-1.0"
 | 
			
		||||
      RUBY
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    Formulary.core_path(name).tap do |formula_path|
 | 
			
		||||
 | 
			
		||||
@ -3,10 +3,22 @@ module Utils
 | 
			
		||||
    popen(args, "rb", options, &block)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def self.safe_popen_read(*args, **options, &block)
 | 
			
		||||
    output = popen_read(*args, **options, &block)
 | 
			
		||||
    raise ErrorDuringExecution, args unless $CHILD_STATUS.success?
 | 
			
		||||
    output
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def self.popen_write(*args, **options, &block)
 | 
			
		||||
    popen(args, "wb", options, &block)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def self.safe_popen_write(*args, **options, &block)
 | 
			
		||||
    output = popen_write(args, **options, &block)
 | 
			
		||||
    raise ErrorDuringExecution, args unless $CHILD_STATUS.success?
 | 
			
		||||
    output
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def self.popen(args, mode, options = {})
 | 
			
		||||
    IO.popen("-", mode) do |pipe|
 | 
			
		||||
      if pipe
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user