Merge pull request #4382 from sjackman/keg_relocate
Relocate bottles on Linux using patchelf
This commit is contained in:
commit
3e85afc8b7
@ -87,11 +87,20 @@ module Homebrew
|
|||||||
end
|
end
|
||||||
|
|
||||||
return merge if args.merge?
|
return merge if args.merge?
|
||||||
|
ensure_relocation_formulae_installed!
|
||||||
ARGV.resolved_formulae.each do |f|
|
ARGV.resolved_formulae.each do |f|
|
||||||
bottle_formula f
|
bottle_formula f
|
||||||
end
|
end
|
||||||
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)
|
def keg_contain?(string, keg, ignores)
|
||||||
@put_string_exists_header, @put_filenames = nil
|
@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)
|
def expand_dependencies(deps)
|
||||||
inherited_options = Hash.new { |hash, key| hash[key] = Options.new }
|
inherited_options = Hash.new { |hash, key| hash[key] = Options.new }
|
||||||
|
pour_bottle = pour_bottle?
|
||||||
|
|
||||||
expanded_deps = Dependency.expand(formula, deps) do |dependent, dep|
|
expanded_deps = Dependency.expand(formula, deps) do |dependent, dep|
|
||||||
inherited_options[dep.name] |= inherited_options_for(dep)
|
inherited_options[dep.name] |= inherited_options_for(dep)
|
||||||
@ -480,6 +481,7 @@ class FormulaInstaller
|
|||||||
dependent,
|
dependent,
|
||||||
inherited_options.fetch(dependent.name, []),
|
inherited_options.fetch(dependent.name, []),
|
||||||
)
|
)
|
||||||
|
pour_bottle = true if install_bottle_for?(dep.to_formula, build)
|
||||||
|
|
||||||
if dep.prune_from_option?(build)
|
if dep.prune_from_option?(build)
|
||||||
Dependency.prune
|
Dependency.prune
|
||||||
@ -494,6 +496,16 @@ class FormulaInstaller
|
|||||||
end
|
end
|
||||||
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]] }
|
expanded_deps.map { |dep| [dep, inherited_options[dep.name]] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -175,6 +175,10 @@ class Keg
|
|||||||
def self.file_linked_libraries(_file, _string)
|
def self.file_linked_libraries(_file, _string)
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.relocation_formulae
|
||||||
|
[]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
require "extend/os/keg_relocate"
|
require "extend/os/keg_relocate"
|
||||||
|
|||||||
@ -4,6 +4,9 @@ describe "brew bottle", :integration_test do
|
|||||||
expect { brew "install", "--build-bottle", testball }
|
expect { brew "install", "--build-bottle", testball }
|
||||||
.to be_a_success
|
.to be_a_success
|
||||||
|
|
||||||
|
setup_test_formula "patchelf"
|
||||||
|
(HOMEBREW_CELLAR/"patchelf/1.0/bin").mkpath
|
||||||
|
|
||||||
expect { brew "bottle", "--no-rebuild", testball }
|
expect { brew "bottle", "--no-rebuild", testball }
|
||||||
.to output(/Formula not from core or any taps/).to_stderr
|
.to output(/Formula not from core or any taps/).to_stderr
|
||||||
.and not_to_output.to_stdout
|
.and not_to_output.to_stdout
|
||||||
|
|||||||
@ -17,6 +17,9 @@ describe FormulaInstaller do
|
|||||||
expect(formula).to be_bottled
|
expect(formula).to be_bottled
|
||||||
expect(formula).to pour_bottle
|
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
|
described_class.new(formula).install
|
||||||
|
|
||||||
keg = Keg.new(formula.prefix)
|
keg = Keg.new(formula.prefix)
|
||||||
|
|||||||
@ -136,20 +136,26 @@ describe Formulary do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context "with installed Formula" do
|
context "with installed Formula" do
|
||||||
let(:formula) { described_class.factory(formula_path) }
|
before do
|
||||||
let(:installer) { FormulaInstaller.new(formula) }
|
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
|
it "returns a Formula when given a rack" do
|
||||||
installer.install
|
installer.install
|
||||||
|
|
||||||
f = described_class.from_rack(formula.rack)
|
f = described_class.from_rack(installed_formula.rack)
|
||||||
expect(f).to be_kind_of(Formula)
|
expect(f).to be_kind_of(Formula)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns a Formula when given a Keg" do
|
it "returns a Formula when given a Keg" do
|
||||||
installer.install
|
installer.install
|
||||||
|
|
||||||
keg = Keg.new(formula.prefix)
|
keg = Keg.new(installed_formula.prefix)
|
||||||
f = described_class.from_keg(keg)
|
f = described_class.from_keg(keg)
|
||||||
expect(f).to be_kind_of(Formula)
|
expect(f).to be_kind_of(Formula)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -157,6 +157,10 @@ RSpec.shared_context "integration test" do
|
|||||||
url "https://example.com/#{name}-1.0"
|
url "https://example.com/#{name}-1.0"
|
||||||
depends_on "foo"
|
depends_on "foo"
|
||||||
RUBY
|
RUBY
|
||||||
|
when "patchelf"
|
||||||
|
content = <<~RUBY
|
||||||
|
url "https://example.com/#{name}-1.0"
|
||||||
|
RUBY
|
||||||
end
|
end
|
||||||
|
|
||||||
Formulary.core_path(name).tap do |formula_path|
|
Formulary.core_path(name).tap do |formula_path|
|
||||||
|
|||||||
@ -3,10 +3,22 @@ module Utils
|
|||||||
popen(args, "rb", options, &block)
|
popen(args, "rb", options, &block)
|
||||||
end
|
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)
|
def self.popen_write(*args, **options, &block)
|
||||||
popen(args, "wb", options, &block)
|
popen(args, "wb", options, &block)
|
||||||
end
|
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 = {})
|
def self.popen(args, mode, options = {})
|
||||||
IO.popen("-", mode) do |pipe|
|
IO.popen("-", mode) do |pipe|
|
||||||
if pipe
|
if pipe
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user