Relocate bottles using install_name_tool.

This has two parts:

1. Bottles are temporarily relocated on bottling and tested if that is
sufficient for them to contain no longer reference the prefix or
cellar. If so, they are marked as relocatable.
2. On installation if bottles are marked as relocatable they will be
relocated using install_name_tool to the current prefix and cellar.

Closes Homebrew/homebrew#18374.
This commit is contained in:
Mike McQuaid 2013-03-11 18:56:26 +00:00
parent 258d70028f
commit 0f9910d352
5 changed files with 98 additions and 19 deletions

View File

@ -1,8 +1,13 @@
require 'formula' require 'formula'
require 'bottles' require 'bottles'
require 'tab' require 'tab'
require 'keg'
module Homebrew extend self module Homebrew extend self
def keg_contains string, keg
quiet_system 'fgrep', '--recursive', '--quiet', '--max-count=1', string, keg
end
def bottle_formula f def bottle_formula f
unless f.installed? unless f.installed?
return ofail "Formula not installed: #{f.name}" return ofail "Formula not installed: #{f.name}"
@ -18,23 +23,48 @@ module Homebrew extend self
bottle_path = Pathname.pwd/filename bottle_path = Pathname.pwd/filename
sha1 = nil sha1 = nil
prefix = HOMEBREW_PREFIX.to_s
tmp_prefix = '/tmp'
cellar = HOMEBREW_CELLAR.to_s
tmp_cellar = '/tmp/Cellar'
HOMEBREW_CELLAR.cd do HOMEBREW_CELLAR.cd do
ohai "Bottling #{f.name} #{f.version}..." ohai "Bottling #{f.name} #{f.version}..."
bottle_relocatable = !quiet_system(
'grep', '--recursive', '--quiet', '--max-count=1',
HOMEBREW_CELLAR, "#{f.name}/#{f.version}")
cellar = nil
if bottle_relocatable
cellar = ':any'
elsif HOMEBREW_CELLAR.to_s != '/usr/local/Cellar'
cellar = "'#{HOMEBREW_CELLAR}'"
end
# Use gzip, faster to compress than bzip2, faster to uncompress than bzip2 # Use gzip, faster to compress than bzip2, faster to uncompress than bzip2
# or an uncompressed tarball (and more bandwidth friendly). # or an uncompressed tarball (and more bandwidth friendly).
safe_system 'tar', 'czf', bottle_path, "#{f.name}/#{f.version}" safe_system 'tar', 'czf', bottle_path, "#{f.name}/#{f.version}"
sha1 = bottle_path.sha1 sha1 = bottle_path.sha1
relocatable = false
keg = Keg.new f.prefix
keg.lock do
# Relocate bottle library references before testing for built-in
# references to the Cellar e.g. Qt's QMake annoyingly does this.
keg.relocate_install_names prefix, tmp_prefix, cellar, tmp_cellar
relocatable = !keg_contains(HOMEBREW_PREFIX, keg)
relocatable = !keg_contains(HOMEBREW_CELLAR, keg) if relocatable
# And do the same thing in reverse to change the library references
# back to how they were.
keg.relocate_install_names tmp_prefix, prefix, tmp_cellar, cellar
end
prefix = cellar = nil
if relocatable
cellar = ':any'
else
if HOMEBREW_PREFIX.to_s != '/usr/local'
prefix = "'#{HOMEBREW_PREFIX}"
end
if HOMEBREW_CELLAR.to_s != '/usr/local/Cellar'
cellar = "'#{HOMEBREW_CELLAR}'"
end
end
puts "./#{filename}" puts "./#{filename}"
puts "bottle do" puts "bottle do"
puts " prefix #{prefix}" if prefix
puts " cellar #{cellar}" if cellar puts " cellar #{cellar}" if cellar
puts " revision #{bottle_revision}" if bottle_revision > 0 puts " revision #{bottle_revision}" if bottle_revision > 0
puts " sha1 '#{sha1}' => :#{MacOS.cat}" puts " sha1 '#{sha1}' => :#{MacOS.cat}"

View File

@ -96,13 +96,13 @@ class FormulaInstaller
begin begin
if pour_bottle? if pour_bottle?
pour pour
poured_bottle = true @poured_bottle = true
end end
rescue rescue
opoo "Bottle installation failed: building from source." opoo "Bottle installation failed: building from source."
end end
unless poured_bottle unless @poured_bottle
build build
clean clean
end end
@ -353,6 +353,17 @@ class FormulaInstaller
def fix_install_names def fix_install_names
Keg.new(f.prefix).fix_install_names Keg.new(f.prefix).fix_install_names
if @poured_bottle
old_prefix = f.bottle.prefix
new_prefix = HOMEBREW_PREFIX.to_s
old_cellar = f.bottle.cellar
new_cellar = HOMEBREW_CELLAR.to_s
if old_prefix != new_prefix or old_cellar != new_cellar
Keg.new(f.prefix).relocate_install_names \
old_prefix, new_prefix, old_cellar, new_cellar
end
end
rescue Exception => e rescue Exception => e
onoe "Failed to fix install names" onoe "Failed to fix install names"
puts "The formula built, but you may encounter issues using it or linking other" puts "The formula built, but you may encounter issues using it or linking other"

View File

@ -85,6 +85,7 @@ class Bottle < SoftwareSpec
def initialize def initialize
super super
@revision = 0 @revision = 0
@prefix = '/usr/local'
@cellar = '/usr/local/Cellar' @cellar = '/usr/local/Cellar'
@cat_without_underscores = false @cat_without_underscores = false
end end
@ -117,6 +118,10 @@ class Bottle < SoftwareSpec
val.nil? ? @root_url : @root_url = val val.nil? ? @root_url : @root_url = val
end end
def prefix val=nil
val.nil? ? @prefix : @prefix = val
end
def cellar val=nil def cellar val=nil
val.nil? ? @cellar : @cellar = val val.nil? ? @cellar : @cellar = val
end end

View File

@ -2,7 +2,7 @@ class Keg
def fix_install_names def fix_install_names
return unless MACOS return unless MACOS
mach_o_files.each do |file| mach_o_files.each do |file|
bad_install_names_for file do |id, bad_names| install_names_for file do |id, bad_names|
file.ensure_writable do file.ensure_writable do
system MacOS.locate("install_name_tool"), "-id", id, file if file.dylib? system MacOS.locate("install_name_tool"), "-id", id, file if file.dylib?
@ -31,13 +31,50 @@ class Keg
end end
end end
def relocate_install_names old_prefix, new_prefix, old_cellar, new_cellar
mach_o_files.each do |file|
install_names_for(file, relocate_reject_proc(old_prefix)) do |id, old_prefix_names|
file.ensure_writable do
new_prefix_id = id.to_s.gsub old_prefix, new_prefix
system MacOS.locate("install_name_tool"), "-id", new_prefix_id, file if file.dylib?
old_prefix_names.each do |old_prefix_name|
new_prefix_name = old_prefix_name.to_s.gsub old_prefix, new_prefix
system MacOS.locate("install_name_tool"), "-change", old_prefix_name, new_prefix_name, file
end
end
end
install_names_for(file, relocate_reject_proc(old_cellar)) do |id, old_cellar_names|
file.ensure_writable do
old_cellar_names.each do |old_cellar_name|
new_cellar_name = old_cellar_name.to_s.gsub old_cellar, new_cellar
system MacOS.locate("install_name_tool"), "-change", old_cellar_name, new_cellar_name, file
end
end
end
end
end
private private
OTOOL_RX = /\t(.*) \(compatibility version (\d+\.)*\d+, current version (\d+\.)*\d+\)/ OTOOL_RX = /\t(.*) \(compatibility version (\d+\.)*\d+, current version (\d+\.)*\d+\)/
def lib; join 'lib' end def lib; join 'lib' end
def bad_install_names_for file def default_reject_proc
Proc.new do |fn|
# Don't fix absolute paths unless they are rooted in the build directory
tmp = ENV['HOMEBREW_TEMP'] ? Regexp.escape(ENV['HOMEBREW_TEMP']) : '/tmp'
fn[0,1] == '/' and not %r[^#{tmp}] === fn
end
end
def relocate_reject_proc(path)
Proc.new { |fn| not fn.start_with?(path) }
end
def install_names_for file, reject_proc=default_reject_proc
ENV['HOMEBREW_MACH_O_FILE'] = file.to_s # solves all shell escaping problems ENV['HOMEBREW_MACH_O_FILE'] = file.to_s # solves all shell escaping problems
install_names = `#{MacOS.locate("otool")} -L "$HOMEBREW_MACH_O_FILE"`.split "\n" install_names = `#{MacOS.locate("otool")} -L "$HOMEBREW_MACH_O_FILE"`.split "\n"
@ -49,12 +86,7 @@ class Keg
install_names.compact! install_names.compact!
install_names.reject!{ |fn| fn =~ /^@(loader_|executable_|r)path/ } install_names.reject!{ |fn| fn =~ /^@(loader_|executable_|r)path/ }
install_names.reject!{ |fn| reject_proc.call(fn) }
# Don't fix absolute paths unless they are rooted in the build directory
install_names.reject! do |fn|
tmp = ENV['HOMEBREW_TEMP'] ? Regexp.escape(ENV['HOMEBREW_TEMP']) : '/tmp'
fn[0,1] == '/' and not %r[^#{tmp}] === fn
end
# the shortpath ensures that library upgrades dont break installed tools # the shortpath ensures that library upgrades dont break installed tools
relative_path = Pathname.new(file).relative_path_from(self) relative_path = Pathname.new(file).relative_path_from(self)

View File

@ -221,6 +221,7 @@ class AllCatsBottleSpecTestBall < Formula
sha1 '482e737739d946b7c8cbaf127d9ee9c148b999f5' sha1 '482e737739d946b7c8cbaf127d9ee9c148b999f5'
bottle do bottle do
prefix '/private/tmp/testbrew/prefix'
cellar '/private/tmp/testbrew/cellar' cellar '/private/tmp/testbrew/cellar'
sha1 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' => :snow_leopard_32 sha1 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' => :snow_leopard_32
sha1 'faceb00cfaceb00cfaceb00cfaceb00cfaceb00c' => :snow_leopard sha1 'faceb00cfaceb00cfaceb00cfaceb00cfaceb00c' => :snow_leopard