add link_overwrite DSL

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-*"

During FormulaInstaller#link, the whitelist conflict files will be
backup into HOMEBREW_CACHE/Backup
This commit is contained in:
Xu Cheng 2015-08-23 16:35:51 +08:00
parent 6362b5fdba
commit 09c810c7f4
2 changed files with 60 additions and 2 deletions

View File

@ -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

View File

@ -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