 70d7c0c122
			
		
	
	
		70d7c0c122
		
	
	
	
	
		
			
			Refusing to unlink files from another keg introduced issues when files changed between formula versions; for instance, this introduced issues when upgrading from gtk+ 2.24.10 to 2.24.11. See Homebrew/homebrew#12778.
		
			
				
	
	
		
			193 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			193 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| require 'extend/pathname'
 | |
| 
 | |
| class Keg < Pathname
 | |
|   def initialize path
 | |
|     super path
 | |
|     raise "#{to_s} is not a valid keg" unless parent.parent.realpath == HOMEBREW_CELLAR.realpath
 | |
|     raise "#{to_s} is not a directory" unless directory?
 | |
|   end
 | |
| 
 | |
|   # locale-specific directories have the form language[_territory][.codeset][@modifier]
 | |
|   LOCALEDIR_RX = /(locale|man)\/([a-z]{2}|C|POSIX)(_[A-Z]{2})?(\.[a-zA-Z\-0-9]+(@.+)?)?/
 | |
|   INFOFILE_RX = %r[info/([^.].*?\.info|dir)$]
 | |
| 
 | |
|   # if path is a file in a keg then this will return the containing Keg object
 | |
|   def self.for path
 | |
|     path = path.realpath
 | |
|     while not path.root?
 | |
|       return Keg.new(path) if path.parent.parent == HOMEBREW_CELLAR.realpath
 | |
|       path = path.parent.realpath # realpath() prevents root? failing
 | |
|     end
 | |
|     raise NotAKegError, "#{path} is not inside a keg"
 | |
|   end
 | |
| 
 | |
|   def uninstall
 | |
|     chmod_R 0777 # ensure we have permission to delete
 | |
|     rmtree
 | |
|     parent.rmdir_if_possible
 | |
|   end
 | |
| 
 | |
|   def unlink
 | |
|     n=0
 | |
| 
 | |
|     %w[bin etc lib include sbin share var].map{ |d| self/d }.each do |src|
 | |
|       next unless src.exist?
 | |
|       src.find do |src|
 | |
|         next if src == self
 | |
|         dst=HOMEBREW_PREFIX+src.relative_path_from(self)
 | |
|         next unless dst.symlink?
 | |
|         dst.uninstall_info if dst.to_s =~ INFOFILE_RX and ENV['HOMEBREW_KEEP_INFO']
 | |
|         dst.unlink
 | |
|         dst.parent.rmdir_if_possible
 | |
|         n+=1
 | |
|         Find.prune if src.directory?
 | |
|       end
 | |
|     end
 | |
|     linked_keg_record.unlink if linked_keg_record.exist?
 | |
|     n
 | |
|   end
 | |
| 
 | |
|   def fname
 | |
|     parent.basename.to_s
 | |
|   end
 | |
| 
 | |
|   def linked_keg_record
 | |
|     @linked_keg_record ||= HOMEBREW_REPOSITORY/"Library/LinkedKegs"/fname
 | |
|   end
 | |
| 
 | |
|   def linked?
 | |
|     linked_keg_record.directory? and self == linked_keg_record.realpath
 | |
|   end
 | |
| 
 | |
|   def link mode=nil
 | |
|     raise "Cannot link #{fname}\nAnother version is already linked: #{linked_keg_record.realpath}" if linked_keg_record.directory?
 | |
| 
 | |
|     $n=0
 | |
|     $d=0
 | |
| 
 | |
|     share_mkpaths=%w[aclocal doc info locale man]+(1..8).collect{|x|"man/man#{x}"}
 | |
|     # cat pages are rare, but exist so the directories should be created
 | |
|     share_mkpaths << (1..8).collect{ |x| "man/cat#{x}" }
 | |
| 
 | |
|     # yeah indeed, you have to force anything you need in the main tree into
 | |
|     # these dirs REMEMBER that *NOT* everything needs to be in the main tree
 | |
|     link_dir('etc', mode) {:mkpath}
 | |
|     link_dir('bin', mode) {:skip_dir}
 | |
|     link_dir('sbin', mode) {:skip_dir}
 | |
|     link_dir('include', mode) {:link}
 | |
| 
 | |
|     link_dir('share', mode) do |path|
 | |
|       case path.to_s
 | |
|       when 'locale/locale.alias' then :skip_file
 | |
|       when INFOFILE_RX then ENV['HOMEBREW_KEEP_INFO'] ? :info : :skip_file
 | |
|       when LOCALEDIR_RX then :mkpath
 | |
|       when *share_mkpaths then :mkpath
 | |
|       when /^zsh/ then :mkpath
 | |
|       else :link
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     link_dir('lib', mode) do |path|
 | |
|       case path.to_s
 | |
|       when 'charset.alias' then :skip_file
 | |
|       # pkg-config database gets explicitly created
 | |
|       when 'pkgconfig' then :mkpath
 | |
|       # lib/language folders also get explicitly created
 | |
|       when /^gdk-pixbuf/ then :mkpath
 | |
|       when 'ghc' then :mkpath
 | |
|       when 'lua' then :mkpath
 | |
|       when 'node' then :mkpath
 | |
|       when /^ocaml/ then :mkpath
 | |
|       when /^perl5/ then :mkpath
 | |
|       when 'php' then :mkpath
 | |
|       when /^python[23]\.\d/ then :mkpath
 | |
|       when 'ruby' then :mkpath
 | |
|       # Everything else is symlinked to the cellar
 | |
|       else :link
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     linked_keg_record.make_relative_symlink(self) unless mode == :dryrun
 | |
| 
 | |
|     return $n + $d
 | |
|   end
 | |
| 
 | |
| protected
 | |
|   def resolve_any_conflicts dst
 | |
|     # if it isn't a directory then a severe conflict is about to happen. Let
 | |
|     # it, and the exception that is generated will message to the user about
 | |
|     # the situation
 | |
|     if dst.symlink? and dst.directory?
 | |
|       src = (dst.parent+dst.readlink).cleanpath
 | |
|       keg = Keg.for(src)
 | |
|       dst.unlink
 | |
|       keg.link_dir(src) { :mkpath }
 | |
|       return true
 | |
|     end
 | |
|   rescue NotAKegError
 | |
|     puts "Won't resolve conflicts for symlink #{dst} as it doesn't resolve into the Cellar" if ARGV.verbose?
 | |
|   end
 | |
| 
 | |
|   def make_relative_symlink dst, src, mode=nil
 | |
|     if dst.exist? and dst.realpath == src.realpath
 | |
|       puts "Skipping; already exists: #{dst}" if ARGV.verbose?
 | |
|     # cf. git-clean -n: list files to delete, don't really link or delete
 | |
|     elsif mode == :dryrun
 | |
|       puts dst if dst.exist?
 | |
|       return
 | |
|     else
 | |
|       dst.delete if mode == :force && dst.exist?
 | |
|       dst.make_relative_symlink src
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   # symlinks the contents of self+foo recursively into /usr/local/foo
 | |
|   def link_dir foo, mode=nil
 | |
|     root = self+foo
 | |
|     return unless root.exist?
 | |
| 
 | |
|     root.find do |src|
 | |
|       next if src == root
 | |
| 
 | |
|       dst = HOMEBREW_PREFIX+src.relative_path_from(self)
 | |
|       dst.extend ObserverPathnameExtension
 | |
| 
 | |
|       if src.file?
 | |
|         Find.prune if File.basename(src) == '.DS_Store'
 | |
| 
 | |
|         case yield src.relative_path_from(root)
 | |
|         when :skip_file, nil
 | |
|           Find.prune
 | |
|         when :info
 | |
|           next if File.basename(src) == 'dir' # skip historical local 'dir' files
 | |
|           make_relative_symlink dst, src, mode
 | |
|           dst.install_info
 | |
|         else
 | |
|           make_relative_symlink dst, src, mode
 | |
|         end
 | |
|       elsif src.directory?
 | |
|         # if the dst dir already exists, then great! walk the rest of the tree tho
 | |
|         next if dst.directory? and not dst.symlink?
 | |
| 
 | |
|         # no need to put .app bundles in the path, the user can just use
 | |
|         # spotlight, or the open command and actual mac apps use an equivalent
 | |
|         Find.prune if src.extname.to_s == '.app'
 | |
| 
 | |
|         case yield src.relative_path_from(root)
 | |
|         when :skip_dir
 | |
|           Find.prune
 | |
|         when :mkpath
 | |
|           dst.mkpath unless resolve_any_conflicts(dst)
 | |
|         else
 | |
|           unless resolve_any_conflicts(dst)
 | |
|             make_relative_symlink dst, src, mode
 | |
|             Find.prune
 | |
|           end
 | |
|         end
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 | |
| 
 | |
| require 'keg_fix_install_names'
 |