# frozen_string_literal: true#--# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.# All rights reserved.# See LICENSE.txt for permissions.#++require"fileutils"require_relative"../rubygems"require_relative"installer_uninstaller_utils"require_relative"dependency_list"require_relative"user_interaction"### An Uninstaller.## The uninstaller fires pre and post uninstall hooks. Hooks can be added# either through a rubygems_plugin.rb file in an installed gem or via a# rubygems/defaults/#{RUBY_ENGINE}.rb or rubygems/defaults/operating_system.rb# file. See Gem.pre_uninstall and Gem.post_uninstall for details.classGem::UninstallerincludeGem::UserInteractionincludeGem::InstallerUninstallerUtils### The directory a gem's executables will be installed intoattr_reader:bin_dir### The gem repository the gem will be uninstalled fromattr_reader:gem_home### The Gem::Specification for the gem being uninstalled, only set during# #uninstall_gemattr_reader:spec### Constructs an uninstaller that will uninstall +gem+definitialize(gem,options={})# TODO: document the valid options@gem=gem@version=options[:version]||Gem::Requirement.default@install_dir=options[:install_dir]@gem_home=File.realpath(@install_dir||Gem.dir)@user_dir=File.exist?(Gem.user_dir)?File.realpath(Gem.user_dir):Gem.user_dir@force_executables=options[:executables]@force_all=options[:all]@force_ignore=options[:ignore]@bin_dir=options[:bin_dir]@format_executable=options[:format_executable]@abort_on_dependent=options[:abort_on_dependent]# Indicate if development dependencies should be checked when# uninstalling. (default: false)#@check_dev=options[:check_dev]ifoptions[:force]@force_all=true@force_ignore=trueend# only add user directory if install_dir is not set@user_install=false@user_install=options[:user_install]unless@install_dir# Optimization: populated during #uninstall@default_specs_matching_uninstall_params=[]end### Performs the uninstall of the gem. This removes the spec, the Gem# directory, and the cached .gem file.defuninstalldependency=Gem::Dependency.new@gem,@versionlist=[]specification_record.stubs.eachdo|spec|nextunlessdependency.matches_spec?speclist<<specendiflist.empty?raiseGem::InstallError,"gem #{@gem.inspect} is not installed"enddefault_specs,list=list.partition(&:default_gem?)warn_cannot_uninstall_default_gems(default_specs-list)@default_specs_matching_uninstall_params=default_specs.map(&:to_spec)list,other_repo_specs=list.partitiondo|spec|@gem_home==spec.base_dir||(@user_install&&spec.base_dir==@user_dir)endlist.sort!iflist.empty?returnunlessother_repo_specs.any?other_repos=other_repo_specs.map(&:base_dir).uniqmessage=["#{@gem} is not installed in GEM_HOME, try:"]message.concatother_repos.map{|repo|"\tgem uninstall -i #{repo}#{@gem}"}raiseGem::InstallError,message.join("\n")elsif@force_allremove_alllistelsiflist.size>1gem_names=list.map(&:full_name_with_location)gem_names<<"All versions"say_,index=choose_from_list"Select gem to uninstall:",gem_namesifindex==list.sizeremove_alllistelsifindex&&index>=0&&index<list.sizeuninstall_gemlist[index]elsesay"Error: must enter a number [1-#{list.size+1}]"endelseuninstall_gemlist.firstendend### Uninstalls gem +spec+defuninstall_gem(stub)spec=stub.to_spec@spec=specunlessdependencies_ok?specifabort_on_dependent?||!ask_if_ok(spec)raiseGem::DependencyRemovalException,"Uninstallation aborted due to dependent gem(s)"endendGem.pre_uninstall_hooks.eachdo|hook|hook.callselfendremove_executables@specremove_plugins@specremove@specspecification_record.remove_spec(stub)regenerate_pluginsGem.post_uninstall_hooks.eachdo|hook|hook.callselfend@spec=nilend### Removes installed executables and batch files (windows only) for +spec+.defremove_executables(spec)returnifspec.executables.empty?||default_spec_matches?(spec)executables=spec.executables.clone# Leave any executables created by other installed versions# of this gem installed.list=Gem::Specification.find_alldo|s|s.name==spec.name&&s.version!=spec.versionendlist.eachdo|s|s.executables.eachdo|exe_name|executables.deleteexe_nameendendreturnifexecutables.empty?executables=executables.map{|exec|formatted_program_filenameexec}remove=if@force_executables.nil?ask_yes_no("Remove executables:\n"\"\t#{executables.join", "}\n\n"\"in addition to the gem?",true)else@force_executablesendifremovebin_dir=@bin_dir||Gem.bindir(spec.base_dir)raiseGem::FilePermissionError,bin_dirunlessFile.writable?bin_direxecutables.eachdo|exe_name|say"Removing #{exe_name}"exe_file=File.joinbin_dir,exe_namesafe_delete{FileUtils.rmexe_file}safe_delete{FileUtils.rm"#{exe_file}.bat"}endelsesay"Executables and scripts will remain installed."endend### Removes all gems in +list+.## NOTE: removes uninstalled gems from +list+.defremove_all(list)list.each{|spec|uninstall_gemspec}end### spec:: the spec of the gem to be uninstalleddefremove(spec)unlesspath_ok?(@gem_home,spec)||(@user_install&&path_ok?(@user_dir,spec))e=Gem::GemNotInHomeException.new\"Gem '#{spec.full_name}' is not installed in directory #{@gem_home}"e.spec=specraiseeendraiseGem::FilePermissionError,spec.base_dirunlessFile.writable?(spec.base_dir)full_gem_path=spec.full_gem_pathexclusions=[]ifdefault_spec_matches?(spec)&&spec.executables.any?exclusions=spec.executables.map{|exe|File.join(spec.bin_dir,exe)}exclusions<<File.dirname(exclusions.last)untilexclusions.last==full_gem_pathendsafe_delete{rm_rfull_gem_path,exclusions: exclusions}safe_delete{FileUtils.rm_rspec.extension_dir}old_platform_name=spec.original_namegem=spec.cache_filegem=File.join(spec.cache_dir,"#{old_platform_name}.gem")unlessFile.exist?gemsafe_delete{FileUtils.rm_rgem}beginGem::RDoc.new(spec).removerescueNameErrorendgemspec=spec.spec_fileunlessFile.exist?gemspecgemspec=File.join(File.dirname(gemspec),"#{old_platform_name}.gemspec")endsafe_delete{FileUtils.rm_rgemspec}announce_deletion_of(spec)end### Remove any plugin wrappers for +spec+.defremove_plugins(spec)# :nodoc:returnifspec.plugins.empty?remove_plugins_for(spec,plugin_dir_for(spec))end### Regenerates plugin wrappers after removal.defregenerate_pluginslatest=specification_record.latest_spec_for(@spec.name)returniflatest.nil?regenerate_plugins_for(latest,plugin_dir_for(@spec))end### Is +spec+ in +gem_dir+?defpath_ok?(gem_dir,spec)full_path=File.joingem_dir,"gems",spec.full_nameoriginal_path=File.joingem_dir,"gems",spec.original_namefull_path==spec.full_gem_path||original_path==spec.full_gem_pathend### Returns true if it is OK to remove +spec+ or this is a forced# uninstallation.defdependencies_ok?(spec)# :nodoc:returntrueif@force_ignoredeplist=Gem::DependencyList.from_specsdeplist.ok_to_remove?(spec.full_name,@check_dev)end### Should the uninstallation abort if a dependency will go unsatisfied?## See ::new.defabort_on_dependent?# :nodoc:@abort_on_dependentend### Asks if it is OK to remove +spec+. Returns true if it is OK.defask_if_ok(spec)# :nodoc:msg=[""]msg<<"You have requested to uninstall the gem:"msg<<"\t#{spec.full_name}"msg<<""siblings=Gem::Specification.selectdo|s|s.name==spec.name&&s.full_name!=spec.full_nameendspec.dependent_gems(@check_dev).eachdo|dep_spec,dep,_satlist|unlesssiblings.any?{|s|s.satisfies_requirement?dep}msg<<"#{dep_spec.name}-#{dep_spec.version} depends on #{dep}"endendmsg<<"If you remove this gem, these dependencies will not be met."msg<<"Continue with Uninstall?"ask_yes_no(msg.join("\n"),false)end### Returns the formatted version of the executable +filename+defformatted_program_filename(filename)# :nodoc:# TODO perhaps the installer should leave a small manifest# of what it did for us to find rather than trying to recreate# it again.if@format_executablerequire_relative"installer"Gem::Installer.exec_format%File.basename(filename)elsefilenameendenddefsafe_delete(&block)block.callrescueErrno::ENOENTnilrescueErrno::EPERMe=Gem::UninstallError.newe.spec=@specraiseeendprivatedefrm_r(path,exclusions:)FileUtils::Entry_.new(path).postorder_traversedo|ent|ent.removeunlessexclusions.include?(ent.path)endenddefspecification_record@specification_record||=@install_dir?Gem::SpecificationRecord.from_path(@install_dir):Gem::Specification.specification_recordenddefannounce_deletion_of(spec)name=spec.full_namesay"Successfully uninstalled #{name}"ifdefault_spec_matches?(spec)say("There was both a regular copy and a default copy of #{name}. The "\"regular copy was successfully uninstalled, but the default copy "\"was left around because default gems can't be removed.")endend# @return true if the specs of any default gems are `==` to the given `spec`.defdefault_spec_matches?(spec)!default_specs_that_match(spec).empty?end# @return [Array] specs of default gems that are `==` to the given `spec`.defdefault_specs_that_match(spec)@default_specs_matching_uninstall_params.select{|default_spec|spec==default_spec}enddefwarn_cannot_uninstall_default_gems(specs)specs.eachdo|spec|say"Gem #{spec.full_name} cannot be uninstalled because it is a default gem"endenddefplugin_dir_for(spec)Gem.plugindir(spec.base_dir)endend