#!/System/Library/Frameworks/Ruby.framework/Versions/Current/usr/bin/ruby require "English" require "fileutils" require "optparse" require "pathname" # Default options options = { :force => false, :quiet => false, :dry_run => false, :skip_cache_and_logs => false, } # global status to indicate whether there is anything wrong. @failed = false module Tty module_function def blue bold 34 end def red bold 31 end def reset escape 0 end def bold(code = 39) escape "1;#{code}" end def escape(code) "\033[#{code}m" if STDOUT.tty? end end class Array def shell_s cp = dup first = cp.shift cp.map { |arg| arg.gsub " ", "\\ " }.unshift(first).join(" ") end end class Pathname def resolved_path symlink? ? dirname+readlink : self end def /(other) self + other.to_s end def pretty_print if symlink? puts to_s + " -> " + resolved_path.to_s elsif directory? puts to_s + "/" else puts to_s end end end def ohai(*args) puts "#{Tty.blue}==>#{Tty.bold} #{args.shell_s}#{Tty.reset}" end def warn(warning) puts "#{Tty.red}Warning#{Tty.reset}: #{warning.chomp}" end def system(*args) return if Kernel.system(*args) warn "Failed during: #{args.shell_s}" @failed = true end ####################################################################### script homebrew_prefix_candidates = [] OptionParser.new do |opts| opts.banner = "Homebrew Uninstaller\nUsage: ./uninstall [options]" opts.summary_width = 16 opts.on("-pPATH", "--path=PATH", "Sets Homebrew prefix. Defaults to /usr/local.") do |p| homebrew_prefix_candidates << Pathname.new(p) end opts.on("--skip-cache-and-logs", "Skips removal of HOMEBREW_CACHE and HOMEBREW_LOGS.") do options[:skip_cache_and_logs] = true end opts.on("-f", "--force", "Uninstall without prompting.") { options[:force] = true } opts.on("-q", "--quiet", "Suppress all output.") { options[:quiet] = true } opts.on("-d", "--dry-run", "Simulate uninstall but don't remove anything.") do options[:dry_run] = true end opts.on_tail("-h", "--help", "Display this message.") do puts opts exit end end.parse! if homebrew_prefix_candidates.empty? # Attempt to locate Homebrew unless `--path` is passed prefix = begin `brew --prefix` rescue "" end homebrew_prefix_candidates << Pathname.new(prefix.strip) unless prefix.empty? prefix = begin begin `command -v brew` rescue `which brew` end rescue "" end homebrew_prefix_candidates << Pathname.new(prefix.strip).dirname.parent unless prefix.empty? homebrew_prefix_candidates << Pathname.new("/usr/local") # Homebrew default path homebrew_prefix_candidates << Pathname.new("#{ENV["HOME"]}/.linuxbrew") # Linuxbrew default path end HOMEBREW_PREFIX = homebrew_prefix_candidates.find do |p| next unless p.directory? if p.to_s == "/usr/local" && File.exist?("/usr/local/Homebrew/.git") next true end (p/".git").exist? || (p/"bin/brew").executable? end abort "Failed to locate Homebrew!" if HOMEBREW_PREFIX.nil? HOMEBREW_REPOSITORY = if (HOMEBREW_PREFIX/".git").exist? (HOMEBREW_PREFIX/".git").realpath.dirname elsif (HOMEBREW_PREFIX/"bin/brew").exist? (HOMEBREW_PREFIX/"bin/brew").realpath.dirname.parent end abort "Failed to locate Homebrew!" if HOMEBREW_REPOSITORY.nil? HOMEBREW_CELLAR = if (HOMEBREW_PREFIX/"Cellar").exist? HOMEBREW_PREFIX/"Cellar" else HOMEBREW_REPOSITORY/"Cellar" end gitignore = begin (HOMEBREW_REPOSITORY/".gitignore").read rescue Errno::ENOENT `curl -fsSL https://raw.githubusercontent.com/Homebrew/brew/master/.gitignore` end abort "Failed to fetch Homebrew .gitignore!" if gitignore.empty? homebrew_files = gitignore.split("\n") .select { |line| line.start_with? "!" } .map { |line| line.chomp("/").gsub(%r{^!?/}, "") } .reject { |line| %w[bin share share/doc].include?(line) } .map { |p| HOMEBREW_REPOSITORY/p } if HOMEBREW_PREFIX.to_s != HOMEBREW_REPOSITORY.to_s homebrew_files << HOMEBREW_REPOSITORY homebrew_files += %w[ bin/brew etc/bash_completion.d/brew share/doc/homebrew share/man/man1/brew.1 share/man/man1/brew-cask.1 share/zsh/site-functions/_brew share/zsh/site-functions/_brew_cask var/homebrew ].map { |p| HOMEBREW_PREFIX/p } else homebrew_files << HOMEBREW_REPOSITORY/".git" end homebrew_files << HOMEBREW_CELLAR homebrew_files << HOMEBREW_PREFIX/"Caskroom" unless options[:skip_cache_and_logs] homebrew_files += %W[ #{ENV["HOME"]}/Library/Caches/Homebrew #{ENV["HOME"]}/Library/Logs/Homebrew /Library/Caches/Homebrew #{ENV["HOME"]}/.cache/Homebrew #{ENV["HOMEBREW_CACHE"]} #{ENV["HOMEBREW_LOGS"]} ].map { |p| Pathname.new(p) } end if RUBY_PLATFORM.to_s.downcase.include? "darwin" homebrew_files += %W[ /Applications #{ENV["HOME"]}/Applications ].map { |p| Pathname.new(p) }.select(&:directory?).map do |p| p.children.select do |app| app.resolved_path.to_s.start_with? HOMEBREW_CELLAR.to_s end end.flatten end homebrew_files = homebrew_files.select(&:exist?).sort unless options[:quiet] warn "This script #{options[:dry_run] ? "would" : "will"} remove:" homebrew_files.each(&:pretty_print) end if STDIN.tty? && (!options[:force] && !options[:dry_run]) STDERR.print "Are you sure you want to uninstall Homebrew? This will remove your installed packages! [y/N] " abort unless gets.rstrip =~ /y|yes/i end ohai "Removing Homebrew installation..." unless options[:quiet] paths = %w[Frameworks bin etc include lib opt sbin share var] .map { |p| HOMEBREW_PREFIX/p } .select(&:exist?) .map(&:to_s) if paths.any? args = %w[-E] + paths + %w[-regex .*/info/([^.][^/]*\.info|dir)] if options[:dry_run] args << "-print" else args += %w[-exec /bin/bash -c] args << "/usr/bin/install-info --delete --quiet {} \"$(dirname {})/dir\"" args << ";" end puts "Would delete:" if options[:dry_run] system "/usr/bin/find", *args args = paths + %w[-type l -lname */Cellar/*] if options[:dry_run] args << "-print" else args += %w[-exec unlink {} ;] end puts "Would delete:" if options[:dry_run] system "/usr/bin/find", *args end homebrew_files.each do |file| if options[:dry_run] puts "Would delete #{file}" else begin FileUtils.rm_rf(file) rescue => e warn "Failed to delete #{file}" puts e.message @failed = true end end end # Invalidate sudo timestamp before exiting at_exit { Kernel.system "/usr/bin/sudo", "-k" } def sudo(*args) ohai "/usr/bin/sudo", *args system "/usr/bin/sudo", *args end ohai "Removing empty directories..." unless options[:quiet] paths = %w[bin etc include lib opt sbin share var Caskroom Cellar Homebrew Frameworks] .map { |p| HOMEBREW_PREFIX/p } .select(&:exist?) .map(&:to_s) if paths.any? args = paths + %w[-name .DS_Store] if options[:dry_run] args << "-print" else args << "-delete" end puts "Would delete:" if options[:dry_run] sudo "/usr/bin/find", *args args = paths + %w[-depth -type d -empty] if options[:dry_run] args << "-print" else args += %w[-exec rmdir {} ;] end puts "Would remove directories:" if options[:dry_run] sudo "/usr/bin/find", *args end if options[:dry_run] exit else if HOMEBREW_PREFIX.to_s != "/usr/local" && HOMEBREW_PREFIX.exist? sudo "rmdir", HOMEBREW_PREFIX.to_s end if HOMEBREW_PREFIX.to_s != HOMEBREW_REPOSITORY.to_s && HOMEBREW_REPOSITORY.exist? sudo "rmdir", HOMEBREW_REPOSITORY.to_s end end unless options[:quiet] if @failed warn "Homebrew partially uninstalled (but there were steps that failed)!" puts "To finish uninstalling rerun this script with `sudo`." else ohai "Homebrew uninstalled!" end end residual_files = [] residual_files.concat(HOMEBREW_REPOSITORY.children) if HOMEBREW_REPOSITORY.exist? residual_files.concat(HOMEBREW_PREFIX.children) if HOMEBREW_PREFIX.exist? residual_files.uniq! unless residual_files.empty? || options[:quiet] puts "The following possible Homebrew files were not deleted:" residual_files.each(&:pretty_print) puts "You may wish to remove them yourself.\n" end exit 1 if @failed