require 'fileutils' require 'json' require_relative '../lib/const' require_relative '../lib/convenience_functions' require_relative '../lib/package' require_relative '../lib/package_utils' class Command def self.remove(pkg, verbose: false, force: false, only_remove_files: false) device_json = JSON.load_file(File.join(CREW_CONFIG_PATH, 'device.json')) # Make sure the package is actually installed before we attempt to remove it. unless PackageUtils.installed?(pkg.name) puts "Package #{pkg.name} isn't installed.".lightred return end # Do not remove any packages in CREW_ESSENTIAL_PACKAGES, as those are needed for ruby and thus crew to run. # CREW_ESSENTIAL_PACKAGES is nil if overriding package upgrade list... return if CREW_ESSENTIAL_PACKAGES.nil? if CREW_ESSENTIAL_PACKAGES.include?(pkg.name) && !force # Exit with failure if attempt to remove an essential package # is made. abort <<~ESSENTIAL_PACKAGE_WARNING_EOF.gsub(/^(?=\w)/, ' ').chomp.lightred #{pkg.name.capitalize} is considered an essential package needed for Chromebrew to function and thus cannot be removed. ESSENTIAL_PACKAGE_WARNING_EOF end # Check whether the removal breaks dependency of other installed packages unless force pkgs_that_need_it = [] device_json['installed_packages'].each do |installed_pkg_info| pkg_file = File.join(CREW_PACKAGES_PATH, "#{installed_pkg_info['name']}.rb") installed_pkg = Package.load_package(pkg_file) pkgs_that_need_it << installed_pkg.name if installed_pkg.dependencies.key?(pkg.name) end if pkgs_that_need_it.any? abort <<~EOT.chomp.lightred #{pkg.name.capitalize} is required by the following installed packages: #{pkgs_that_need_it.join("\n ")} Use `crew remove --force` if you meant to remove it. EOT end end # Perform any operations required prior to package removal. pkg.preremove unless only_remove_files # Use gem to first try to remove gems... if pkg.name.start_with?('ruby_') @ruby_gem_name = pkg.name.sub('ruby_', '').sub('_', '-') if Kernel.system "gem list -i \"^#{@ruby_gem_name}\$\"", %i[out err] => File::NULL puts "Uninstalling #{@ruby_gem_name} before removing gem files. It's ok if this fails.".orange system "gem uninstall -aIx --abort-on-dependent #{@ruby_gem_name}", exception: false end end # Use pip to first try to remove python packages if package uses pip. if pkg.name.start_with?('py3_') pkg_file = Dir["{#{CREW_LOCAL_REPO_ROOT}/packages,#{CREW_PACKAGES_PATH}}/#{pkg.name}.rb"].max { |a, b| File.mtime(a) <=> File.mtime(b) } system "python3 -s -m pip uninstall #{pkg.name.gsub('py3_', '')} -y", exception: false if Kernel.system "grep -q \"^require 'buildsystems/pip\" #{pkg_file}" end # Remove the files and directories installed by the package. unless pkg.is_fake? Dir.chdir CREW_CONFIG_PATH do # Remove all files installed by the package in CREW_PREFIX and HOME. # # Exceptions: # 1. The file exists in another installed package. filelist_path = File.join(CREW_META_PATH, "#{pkg.name}.filelist") if File.file?(filelist_path) filelist = File.readlines(filelist_path, chomp: true).grep(/^(?!#)/) overlap_files = ConvenienceFunctions.determine_conflicts(pkg.name, filelist_path, verbose: verbose) # essential_files should only contain filelists for packages already installed. essential_files = CREW_ESSENTIAL_PACKAGES.flat_map { |f| File.readlines(File.join(CREW_META_PATH, "#{f}.filelist"), chomp: true) if File.file?(File.join(CREW_META_PATH, "#{f}.filelist")) } overlap_essential_files = filelist & essential_files files_to_remove = filelist - overlap_files.values.flatten - overlap_essential_files if overlap_essential_files.any? warn "The following file(s) will not be deleted as they are required for Chromebrew to work properly:\n".orange warn overlap_essential_files.join("\n").orange warn "\n\n" end if overlap_files.any? warn "The following file(s) in other packages will not be deleted during the removal of #{pkg.name}:\n".orange overlap_files.each_pair do |pkg_name, files| files.each { |file| puts "#{pkg_name}: #{file}".orange } end puts end files_to_remove.each do |file| puts "Removing file #{file}".yellow if verbose FileUtils.remove_file file, exception: false end FileUtils.remove_file filelist_path end # Remove all directories installed by the package. dlist = File.join(CREW_META_PATH, "#{pkg.name}.directorylist") if File.file?(dlist) File.foreach(dlist, chomp: true) do |line| next unless Dir.exist?(line) && Dir.empty?(line) && line.include?(CREW_PREFIX) puts "Removing directory #{line}".yellow if verbose FileUtils.remove_dir line, exception: false end FileUtils.remove_file dlist end end end # Remove the package from the list of installed packages in device.json. puts "Removing package #{pkg.name} from device.json".yellow if verbose device_json['installed_packages'].delete_if { |entry| entry['name'] == pkg.name } # Update device.json with our changes. ConvenienceFunctions.save_json(device_json) # Perform any operations required after package removal. pkg.postremove unless only_remove_files puts "#{pkg.name} removed!".lightgreen end end