1#-- 2# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. 3# All rights reserved. 4# See LICENSE.txt for permissions. 5#++ 6 7require 'fileutils' 8require 'rubygems' 9require 'rubygems/dependency_list' 10require 'rubygems/rdoc' 11require 'rubygems/user_interaction' 12 13## 14# An Uninstaller. 15# 16# The uninstaller fires pre and post uninstall hooks. Hooks can be added 17# either through a rubygems_plugin.rb file in an installed gem or via a 18# rubygems/defaults/#{RUBY_ENGINE}.rb or rubygems/defaults/operating_system.rb 19# file. See Gem.pre_uninstall and Gem.post_uninstall for details. 20 21class Gem::Uninstaller 22 23 include Gem::UserInteraction 24 25 ## 26 # The directory a gem's executables will be installed into 27 28 attr_reader :bin_dir 29 30 ## 31 # The gem repository the gem will be installed into 32 33 attr_reader :gem_home 34 35 ## 36 # The Gem::Specification for the gem being uninstalled, only set during 37 # #uninstall_gem 38 39 attr_reader :spec 40 41 ## 42 # Constructs an uninstaller that will uninstall +gem+ 43 44 def initialize(gem, options = {}) 45 # TODO document the valid options 46 @gem = gem 47 @version = options[:version] || Gem::Requirement.default 48 @gem_home = File.expand_path(options[:install_dir] || Gem.dir) 49 @force_executables = options[:executables] 50 @force_all = options[:all] 51 @force_ignore = options[:ignore] 52 @bin_dir = options[:bin_dir] 53 @format_executable = options[:format_executable] 54 55 # Indicate if development dependencies should be checked when 56 # uninstalling. (default: false) 57 # 58 @check_dev = options[:check_dev] 59 60 if options[:force] 61 @force_all = true 62 @force_ignore = true 63 end 64 65 # only add user directory if install_dir is not set 66 @user_install = false 67 @user_install = options[:user_install] unless options[:install_dir] 68 end 69 70 ## 71 # Performs the uninstall of the gem. This removes the spec, the Gem 72 # directory, and the cached .gem file. 73 74 def uninstall 75 dependency = Gem::Dependency.new @gem, @version 76 77 list = [] 78 79 dirs = 80 Gem::Specification.dirs + 81 [Gem::Specification.default_specifications_dir] 82 83 Gem::Specification.each_spec dirs do |spec| 84 next unless dependency.matches_spec? spec 85 86 list << spec 87 end 88 89 default_specs, list = list.partition do |spec| 90 spec.default_gem? 91 end 92 93 list, other_repo_specs = list.partition do |spec| 94 @gem_home == spec.base_dir or 95 (@user_install and spec.base_dir == Gem.user_dir) 96 end 97 98 if list.empty? then 99 if other_repo_specs.empty? 100 if default_specs.empty? 101 raise Gem::InstallError, "gem #{@gem.inspect} is not installed" 102 else 103 message = 104 "gem #{@gem.inspect} cannot be uninstalled " + 105 "because it is a default gem" 106 raise Gem::InstallError, message 107 end 108 end 109 110 other_repos = other_repo_specs.map { |spec| spec.base_dir }.uniq 111 112 message = ["#{@gem} is not installed in GEM_HOME, try:"] 113 message.concat other_repos.map { |repo| 114 "\tgem uninstall -i #{repo} #{@gem}" 115 } 116 117 raise Gem::InstallError, message.join("\n") 118 elsif @force_all then 119 remove_all list 120 121 elsif list.size > 1 then 122 gem_names = list.collect {|gem| gem.full_name} + ["All versions"] 123 124 say 125 _, index = choose_from_list "Select gem to uninstall:", gem_names 126 127 if index == list.size then 128 remove_all list 129 elsif index >= 0 && index < list.size then 130 uninstall_gem list[index] 131 else 132 say "Error: must enter a number [1-#{list.size+1}]" 133 end 134 else 135 uninstall_gem list.first 136 end 137 end 138 139 ## 140 # Uninstalls gem +spec+ 141 142 def uninstall_gem(spec) 143 @spec = spec 144 145 unless dependencies_ok? spec 146 unless ask_if_ok(spec) 147 raise Gem::DependencyRemovalException, 148 "Uninstallation aborted due to dependent gem(s)" 149 end 150 end 151 152 Gem.pre_uninstall_hooks.each do |hook| 153 hook.call self 154 end 155 156 remove_executables @spec 157 remove @spec 158 159 Gem.post_uninstall_hooks.each do |hook| 160 hook.call self 161 end 162 163 @spec = nil 164 end 165 166 ## 167 # Removes installed executables and batch files (windows only) for 168 # +gemspec+. 169 170 def remove_executables(spec) 171 return if spec.nil? or spec.executables.empty? 172 173 executables = spec.executables.clone 174 175 # Leave any executables created by other installed versions 176 # of this gem installed. 177 178 list = Gem::Specification.find_all { |s| 179 s.name == spec.name && s.version != spec.version 180 } 181 182 list.each do |s| 183 s.executables.each do |exe_name| 184 executables.delete exe_name 185 end 186 end 187 188 return if executables.empty? 189 190 executables = executables.map { |exec| formatted_program_filename exec } 191 192 remove = if @force_executables.nil? then 193 ask_yes_no("Remove executables:\n" + 194 "\t#{executables.join ', '}\n\n" + 195 "in addition to the gem?", 196 true) 197 else 198 @force_executables 199 end 200 201 if remove then 202 bin_dir = @bin_dir || Gem.bindir(spec.base_dir) 203 204 raise Gem::FilePermissionError, bin_dir unless File.writable? bin_dir 205 206 executables.each do |exe_name| 207 say "Removing #{exe_name}" 208 209 exe_file = File.join bin_dir, exe_name 210 211 FileUtils.rm_f exe_file 212 FileUtils.rm_f "#{exe_file}.bat" 213 end 214 else 215 say "Executables and scripts will remain installed." 216 end 217 end 218 219 ## 220 # Removes all gems in +list+. 221 # 222 # NOTE: removes uninstalled gems from +list+. 223 224 def remove_all(list) 225 list.each { |spec| uninstall_gem spec } 226 end 227 228 ## 229 # spec:: the spec of the gem to be uninstalled 230 # list:: the list of all such gems 231 # 232 # Warning: this method modifies the +list+ parameter. Once it has 233 # uninstalled a gem, it is removed from that list. 234 235 def remove(spec) 236 unless path_ok?(@gem_home, spec) or 237 (@user_install and path_ok?(Gem.user_dir, spec)) then 238 e = Gem::GemNotInHomeException.new \ 239 "Gem is not installed in directory #{@gem_home}" 240 e.spec = spec 241 242 raise e 243 end 244 245 raise Gem::FilePermissionError, spec.base_dir unless 246 File.writable?(spec.base_dir) 247 248 FileUtils.rm_rf spec.full_gem_path 249 250 # TODO: should this be moved to spec?... I vote eww (also exists in docmgr) 251 old_platform_name = [spec.name, 252 spec.version, 253 spec.original_platform].join '-' 254 255 gemspec = spec.spec_file 256 257 unless File.exist? gemspec then 258 gemspec = File.join(File.dirname(gemspec), "#{old_platform_name}.gemspec") 259 end 260 261 FileUtils.rm_rf gemspec 262 263 gem = spec.cache_file 264 gem = File.join(spec.cache_dir, "#{old_platform_name}.gem") unless 265 File.exist? gem 266 267 FileUtils.rm_rf gem 268 269 Gem::RDoc.new(spec).remove 270 271 say "Successfully uninstalled #{spec.full_name}" 272 273 Gem::Specification.remove_spec spec 274 end 275 276 ## 277 # Is +spec+ in +gem_dir+? 278 279 def path_ok?(gem_dir, spec) 280 full_path = File.join gem_dir, 'gems', spec.full_name 281 original_path = File.join gem_dir, 'gems', spec.original_name 282 283 full_path == spec.full_gem_path || original_path == spec.full_gem_path 284 end 285 286 def dependencies_ok?(spec) 287 return true if @force_ignore 288 289 deplist = Gem::DependencyList.from_specs 290 deplist.ok_to_remove?(spec.full_name, @check_dev) 291 end 292 293 def ask_if_ok(spec) 294 msg = [''] 295 msg << 'You have requested to uninstall the gem:' 296 msg << "\t#{spec.full_name}" 297 msg << '' 298 299 siblings = Gem::Specification.select do |s| 300 s.name == spec.name && s.full_name != spec.full_name 301 end 302 303 spec.dependent_gems.each do |dep_spec, dep, satlist| 304 unless siblings.any? { |s| s.satisfies_requirement? dep } 305 msg << "#{dep_spec.name}-#{dep_spec.version} depends on #{dep}" 306 end 307 end 308 309 msg << 'If you remove this gem, these dependencies will not be met.' 310 msg << 'Continue with Uninstall?' 311 return ask_yes_no(msg.join("\n"), false) 312 end 313 314 def formatted_program_filename(filename) 315 # TODO perhaps the installer should leave a small manifest 316 # of what it did for us to find rather than trying to recreate 317 # it again. 318 if @format_executable then 319 require 'rubygems/installer' 320 Gem::Installer.exec_format % File.basename(filename) 321 else 322 filename 323 end 324 end 325end 326