brew/Library/Homebrew/keg_fix_install_names.rb
Jack Nagel 94ebe8e747 relocate_install_names: rewrite cellar names before prefix names
The cellar may overlap with the prefix, so if we replace the prefix
first, we will end up with paths like "@@HOMEBREW_PREFIX@@/Cellar"
instead of "@@HOMEBREW_CELLAR@@", which will break on installations
where the cellar and prefix are disjoint.
2013-12-04 22:37:57 -06:00

201 lines
6.3 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class Keg
def fix_install_names options={}
mach_o_files.each do |file|
install_names_for(file, options) do |id, bad_names|
file.ensure_writable do
change_dylib_id(id, file) if file.dylib?
bad_names.each do |bad_name|
new_name = fixed_name(file, bad_name)
unless new_name == bad_name
change_install_name(bad_name, new_name, file)
end
end
end
end
end
end
def relocate_install_names old_prefix, new_prefix, old_cellar, new_cellar, options={}
mach_o_files.each do |file|
install_names_for(file, options, 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
change_install_name(old_cellar_name, new_cellar_name, file)
end
end
end
install_names_for(file, options, 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
change_dylib_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
change_install_name(old_prefix_name, new_prefix_name, file)
end
end
end
end
# Search for pkgconfig .pc files and relocate references to the cellar
old_cellar = "#{old_prefix}/Cellar" if old_cellar == :any
pkgconfig_files.each do |pcfile|
pcfile.ensure_writable do
pcfile.open('rb') do |f|
s = f.read
replace_pkgconfig_file_path(s, old_cellar, new_cellar)
replace_pkgconfig_file_path(s, old_prefix, new_prefix)
f.reopen(pcfile, 'wb')
f.write(s)
end
end
end
end
def change_dylib_id(id, file)
install_name_tool("-id", id, file)
end
def change_install_name(old, new, file)
install_name_tool("-change", old, new, file)
end
# Given old == "/usr/local/Cellar" and new == "/opt/homebrew/Cellar",
# then update lines of the form
# some_variable=/usr/local/Cellar/foo/1.0/lib
# to
# some_variable="/opt/homebrew/Cellar/foo/1.0/lib"
# and add quotes to protect against paths containing spaces.
def replace_pkgconfig_file_path(s, old, new)
return if old == new
s.gsub!(%r[([\S]+)="?#{Regexp.escape(old)}(.*?)"?$], "\\1=\"#{new}\\2\"")
end
# Detects the C++ dynamic libraries in place, scanning the dynamic links
# of the files within the keg. This searches only libs contained within
# lib/, and ignores binaries and other mach-o objects
# Note that this doesn't attempt to distinguish between libstdc++ versions,
# for instance between Apple libstdc++ and GNU libstdc++
def detect_cxx_stdlibs
results = Set.new
mach_o_files.each do |file|
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
private
OTOOL_RX = /\t(.*) \(compatibility version (\d+\.)*\d+, current version (\d+\.)*\d+\)/
def install_name_tool(*args)
system(MacOS.locate("install_name_tool"), *args)
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 (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(Pathname.new(bad_name).basename)) && abs_name.exist?
abs_name.to_s
else
opoo "Could not fix #{bad_name} in #{file}"
bad_name
end
end
def lib; join 'lib' end
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, options, reject_proc=default_reject_proc
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.shift # first line is fluff
install_names.map!{ |s| OTOOL_RX =~ s && $1 }
# Bundles and executables do not have an ID
id = install_names.shift if file.dylib?
install_names.compact!
install_names.reject!{ |fn| fn =~ /^@(loader_|executable_|r)path/ }
install_names.reject!{ |fn| reject_proc.call(fn) }
# the shortpath ensures that library upgrades dont break installed tools
relative_path = Pathname.new(file).relative_path_from(self)
shortpath = HOMEBREW_PREFIX.join(relative_path)
id = if shortpath.exist? and not options[:keg_only]
shortpath
else
"#{HOMEBREW_PREFIX}/opt/#{fname}/#{relative_path}"
end
yield id, install_names
end
def find_dylib name
(join 'lib').find do |pn|
break pn if pn.basename == Pathname.new(name)
end
end
def mach_o_files
mach_o_files = []
dirs = %w{bin lib Frameworks}
dirs.map! { |dir| join(dir) }
dirs.reject! { |dir| not dir.directory? }
dirs.each do |dir|
dir.find do |pn|
next if pn.symlink? or pn.directory?
mach_o_files << pn if pn.dylib? or pn.mach_o_bundle? or pn.mach_o_executable?
end
end
mach_o_files
end
def pkgconfig_files
pkgconfig_files = []
# find .pc files, which are stored in lib/pkgconfig
pc_dir = self/'lib/pkgconfig'
if pc_dir.directory?
pc_dir.find do |pn|
next if pn.symlink? or pn.directory? or pn.extname.to_s != '.pc'
pkgconfig_files << pn
end
end
# find name-config scripts, which can be all over the keg
Pathname.new(self).find do |pn|
next if pn.symlink? or pn.directory?
pkgconfig_files << pn if pn.text_executable? and pn.basename.to_s.end_with? '-config'
end
pkgconfig_files
end
end