diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 278cf65959..8fba7803bd 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -11,6 +11,7 @@ require "install_renamed" require "pkg_version" require "tap" require "formula_renames" +require "keg" # A formula provides instructions and metadata for Homebrew to install a piece # of software. Every Homebrew formula is a {Formula}. @@ -330,7 +331,6 @@ class Formula # The currently installed version for this formula. Will raise an exception # if the formula is not installed. def installed_version - require "keg" Keg.new(installed_prefix).version end @@ -657,6 +657,30 @@ class Formula self.class.skip_clean_paths.include? to_check end + # Sometimes we accidentally install files outside prefix. After we fix that, + # users will get nasty link conflict error. So we create a whitelist here to + # allow overwriting certain files. e.g. + # link_overwrite "bin/foo", "lib/bar" + # link_overwrite "share/man/man1/baz-*" + def link_overwrite?(path) + # Don't overwrite files not created by Homebrew. + return false unless path.stat.uid == File.stat(HOMEBREW_BREW_FILE).uid + # Don't overwrite files belong to other keg. + begin + Keg.for(path) + rescue NotAKegError, Errno::ENOENT + # file doesn't belong to any keg. + else + return false + end + to_check = path.relative_path_from(HOMEBREW_PREFIX).to_s + self.class.link_overwrite_paths.any? do |p| + p == to_check || + to_check.start_with?(p.chomp("/") + "/") || + /^#{Regexp.escape(p).gsub('\*', ".*?")}$/ === to_check + end + end + def skip_cxxstdlib_check? false end @@ -1351,5 +1375,14 @@ class Formula def test(&block) define_method(:test, &block) end + + def link_overwrite(*paths) + paths.flatten! + link_overwrite_paths.merge(paths) + end + + def link_overwrite_paths + @link_overwrite_paths ||= Set.new + end end end diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb index 550fd93ada..3c19f7a149 100644 --- a/Library/Homebrew/formula_installer.rb +++ b/Library/Homebrew/formula_installer.rb @@ -589,9 +589,20 @@ class FormulaInstaller keg.remove_linked_keg_record end + link_overwrite_backup = {} # dict: conflict file -> backup file + backup_dir = HOMEBREW_CACHE/"Backup" + begin keg.link rescue Keg::ConflictError => e + conflict_file = e.dst + if formula.link_overwrite?(conflict_file) && !link_overwrite_backup.key?(conflict_file) + backup_file = backup_dir/conflict_file.relative_path_from(HOMEBREW_PREFIX).to_s + backup_file.parent.mkpath + conflict_file.rename backup_file + link_overwrite_backup[conflict_file] = backup_file + retry + end onoe "The `brew link` step did not complete successfully" puts "The formula built, but is not symlinked into #{HOMEBREW_PREFIX}" puts e @@ -616,10 +627,24 @@ class FormulaInstaller puts e puts e.backtrace if debug? @show_summary_heading = true - ignore_interrupts { keg.unlink } + ignore_interrupts do + keg.unlink + link_overwrite_backup.each do |conflict_file, backup_file| + conflict_file.parent.mkpath + backup_file.rename conflict_file + end + end Homebrew.failed = true raise end + + unless link_overwrite_backup.empty? + opoo "These files were overwritten during `brew link` step:" + puts link_overwrite_backup.keys + puts + puts "They are backup in #{backup_dir}" + @show_summary_heading = true + end end def install_plist