From b9cdfe21fc7c1dbfa2f6ce7a087a47556dfff5df Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Tue, 14 Jul 2015 11:56:34 -0700 Subject: keg_relocate: relocate all text files. Work out what's text and what's not using `file`. Also, rename `keg_fix_install_names` to `keg_relocate` because that's a more accurate description of what it does now. Closes Homebrew/homebrew#41663. Signed-off-by: Mike McQuaid --- Library/Homebrew/keg.rb | 2 +- Library/Homebrew/keg_fix_install_names.rb | 215 ------------------------------ Library/Homebrew/keg_relocate.rb | 171 ++++++++++++++++++++++++ 3 files changed, 172 insertions(+), 216 deletions(-) delete mode 100644 Library/Homebrew/keg_fix_install_names.rb create mode 100644 Library/Homebrew/keg_relocate.rb diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb index 20e24d1ec..cf4be6cc0 100644 --- a/Library/Homebrew/keg.rb +++ b/Library/Homebrew/keg.rb @@ -1,5 +1,5 @@ require "extend/pathname" -require "keg_fix_install_names" +require "keg_relocate" require "formula_lock" require "ostruct" diff --git a/Library/Homebrew/keg_fix_install_names.rb b/Library/Homebrew/keg_fix_install_names.rb deleted file mode 100644 index a6ddb970f..000000000 --- a/Library/Homebrew/keg_fix_install_names.rb +++ /dev/null @@ -1,215 +0,0 @@ -class Keg - PREFIX_PLACEHOLDER = "@@HOMEBREW_PREFIX@@".freeze - CELLAR_PLACEHOLDER = "@@HOMEBREW_CELLAR@@".freeze - - def fix_install_names options={} - mach_o_files.each do |file| - file.ensure_writable do - change_dylib_id(dylib_id_for(file, options), 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? '/' and not 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 - end - - def relocate_install_names old_prefix, new_prefix, old_cellar, new_cellar, options={} - mach_o_files.each do |file| - file.ensure_writable do - if file.dylib? - id = dylib_id_for(file, options).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 - - files = pkgconfig_files | libtool_files | script_files | plist_files - files << tab_file if tab_file.file? - - files.group_by { |f| f.stat.ino }.each_value do |first, *rest| - s = first.open("rb", &:read) - changed = s.gsub!(old_cellar, new_cellar) - changed = s.gsub!(old_prefix, new_prefix) || changed - - begin - first.atomic_write(s) - rescue SystemCallError - first.ensure_writable do - first.open("wb") { |f| f.write(s) } - end - else - rest.each { |file| FileUtils.ln(first, file, :force => true) } - end if changed - end - end - - def change_dylib_id(id, file) - puts "Changing dylib ID of #{file}\n from #{file.dylib_id}\n to #{id}" if ARGV.debug? - install_name_tool("-id", id, file) - end - - def change_install_name(old, new, file) - puts "Changing install name in #{file}\n from #{old}\n to #{new}" if ARGV.debug? - install_name_tool("-change", old, new, file) - 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 - Utils.popen_read("/usr/bin/fgrep", "-lr", string, to_s) do |io| - hardlinks = Set.new - - until io.eof? - file = Pathname.new(io.readline.chomp) - next if file.symlink? - yield file if hardlinks.add? file.stat.ino - end - end - end - - def install_name_tool(*args) - tool = MacOS.locate("install_name_tool") - system(tool, *args) or raise ErrorDuringExecution.new(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 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(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 - 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, options) - # 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) - shortpath = HOMEBREW_PREFIX.join(relative_dirname, basename) - - if shortpath.exist? and not options[:keg_only] - shortpath.to_s - else - opt_record.join(relative_dirname, basename).to_s - end - end - - def find_dylib name - lib.find { |pn| break pn if pn.basename == name } if lib.directory? - end - - def mach_o_files - mach_o_files = [] - path.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 - - mach_o_files - end - - def script_files - script_files = [] - - # find all files with shebangs - find do |pn| - next if pn.symlink? or pn.directory? - script_files << pn if pn.text_executable? - end - - script_files - end - - def pkgconfig_files - pkgconfig_files = [] - - %w[lib share].each do |dir| - pcdir = path.join(dir, "pkgconfig") - - pcdir.find do |pn| - next if pn.symlink? or pn.directory? or pn.extname != '.pc' - pkgconfig_files << pn - end if pcdir.directory? - end - - pkgconfig_files - end - - def libtool_files - libtool_files = [] - - # find .la files, which are stored in lib/ - lib.find do |pn| - next if pn.symlink? or pn.directory? or pn.extname != '.la' - libtool_files << pn - end if lib.directory? - libtool_files - end - - def plist_files - plist_files = [] - - self.find do |pn| - next if pn.symlink? or pn.directory? or pn.extname != '.plist' - plist_files << pn - end - plist_files - end - - def tab_file - join(Tab::FILENAME) - end -end diff --git a/Library/Homebrew/keg_relocate.rb b/Library/Homebrew/keg_relocate.rb new file mode 100644 index 000000000..a66670273 --- /dev/null +++ b/Library/Homebrew/keg_relocate.rb @@ -0,0 +1,171 @@ +class Keg + PREFIX_PLACEHOLDER = "@@HOMEBREW_PREFIX@@".freeze + CELLAR_PLACEHOLDER = "@@HOMEBREW_CELLAR@@".freeze + + def fix_install_names options={} + mach_o_files.each do |file| + file.ensure_writable do + change_dylib_id(dylib_id_for(file, options), 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? '/' and not 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 + end + + def relocate_install_names old_prefix, new_prefix, old_cellar, new_cellar, options={} + mach_o_files.each do |file| + file.ensure_writable do + if file.dylib? + id = dylib_id_for(file, options).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 + + text_files.group_by { |f| f.stat.ino }.each_value do |first, *rest| + s = first.open("rb", &:read) + changed = s.gsub!(old_cellar, new_cellar) + changed = s.gsub!(old_prefix, new_prefix) || changed + + begin + first.atomic_write(s) + rescue SystemCallError + first.ensure_writable do + first.open("wb") { |f| f.write(s) } + end + else + rest.each { |file| FileUtils.ln(first, file, :force => true) } + end if changed + end + end + + def change_dylib_id(id, file) + puts "Changing dylib ID of #{file}\n from #{file.dylib_id}\n to #{id}" if ARGV.debug? + install_name_tool("-id", id, file) + end + + def change_install_name(old, new, file) + puts "Changing install name in #{file}\n from #{old}\n to #{new}" if ARGV.debug? + install_name_tool("-change", old, new, file) + 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 + Utils.popen_read("/usr/bin/fgrep", "-lr", string, to_s) do |io| + hardlinks = Set.new + + until io.eof? + file = Pathname.new(io.readline.chomp) + next if file.symlink? + yield file if hardlinks.add? file.stat.ino + end + end + end + + def install_name_tool(*args) + tool = MacOS.locate("install_name_tool") + system(tool, *args) or raise ErrorDuringExecution.new(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 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(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 + 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, options) + # 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) + shortpath = HOMEBREW_PREFIX.join(relative_dirname, basename) + + if shortpath.exist? and not options[:keg_only] + shortpath.to_s + else + opt_record.join(relative_dirname, basename).to_s + end + end + + def find_dylib name + lib.find { |pn| break pn if pn.basename == name } if lib.directory? + end + + def mach_o_files + mach_o_files = [] + path.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 + + mach_o_files + end + + def text_files + text_files = [] + path.find do |pn| + next if pn.symlink? or pn.directory? + next if Metafiles::EXTENSIONS.include? pn.extname + text_files << pn if Utils.popen_read("/usr/bin/file", pn).include?("text") + end + + text_files + end +end -- cgit v1.2.3