btd
btd

Reputation: 434

Uninstall all gems which are not in a specified list of Gemfile.lock files

I'd like to clean a little system-house. Essentially,

(Gem.all_system_gems - Bundler.referenced_gems(array_of_gemfile_locks)).each do |gem, ver|
  `gem uninstall #{gem} -v #{ver}
end

Any such RubyGems/Bundler methods? Or any known/efficient way of accomplishing the same?

Thanks, Ben

Upvotes: 3

Views: 1437

Answers (4)

btd
btd

Reputation: 434

This code is based off of what @Kashyap answered (Bundler::LockfileParser is a good find). I ended up changing it a bit and wanted to share what I ended up using.

require 'rubygems'
require 'bundler'

LOCK_FILES = %w(/path/to/first/Gemfile.lock /path/to/second/Gemfile.lock)
REVIEWABLE_SHELL_SCRIPT = 'gem_cleaner.csh'

class GemCleaner
  def initialize lock_files, output_file
    @lock_files = lock_files
    @output_file = output_file
  end
  def lock_file_gems
    @lock_files.map do |lock_file| 
      Bundler::LockfileParser.new(File.read(lock_file)).specs.
        map {|s| [s.name, s.version.version] } 
    end.flatten(1).uniq
  end
  def installed_gems
    Gem::Specification.find_all.map {|s| [s.name, s.version.version] }
  end
  def gems_to_uninstall
    installed_gems - lock_file_gems
  end
  def create_shell_script
    File.open(@output_file, 'w', 0744) do |f|
      f.puts "#!/bin/csh"
      gems_to_uninstall.sort.each {|g| f.puts "gem uninstall #{g[0]} -v #{g[1]}" }
    end
  end
end

gc = GemCleaner.new(LOCK_FILES, REVIEWABLE_SHELL_SCRIPT)
gc.create_shell_script

Primary differences are use of Gem::Specification.find_all and output to a shell script so I could review the gems before uninstalling. Oh, and still doing it the old-fashioned OO-way. :)

Leaving selected answer with @Kashyap. Props.

Upvotes: 0

Kashyap
Kashyap

Reputation: 4796

Caution: Severe brain-damage possible.

I put up a version here explaining each function.


# gem_cleaner.rb

require 'bundler'

`touch Gemfile` unless File.exists?("Gemfile")

dot_lockfiles = [ "/path/to/gemfile1.lock", "/path/to/gemfile2.lock" 
  # ..and so on...
]

lockfile_parser = ->(path) do
  Bundler::LockfileParser.new(File.read(path))
end

lockfile_specs = ->(lockfile) { lockfile.specs.map(&:to_s) }

de_parenthesize = ->(string) { string.gsub(/\,|\(|\)/, "") }

uninstaller = ->(string) do
  `gem uninstall #{string.split(" ").map(&de_parenthesize).join(" -v ")}`
end

splitter = ->(string) do
  temp = string.split(" ").map(&de_parenthesize)
  gem_name = temp.shift
  temp.map {|x| "#{gem_name} (#{x})"}
end

# Remove #lazy and #to_a if on Ruby version < 2.0
gems_to_be_kept    = dot_lockfiles.lazy.map(&lockfile_parser).map(&lockfile_specs).to_a.uniq
all_installed_gems = `gem list`.split("\n").map(&splitter).flatten
gems_to_be_uninstalled = all_installed_gems - gems_to_be_kept
gems_to_be_uninstalled.map(&uninstaller)

Why did I write this snippet this way? I happened to see this the other day: http://www.confreaks.com/videos/2382-rmw2013-functional-principles-for-oo-development

Upvotes: 3

georgehemmings
georgehemmings

Reputation: 518

Bundler has a clean command to remove unused gems.

bundle clean --force

This will remove all gems that are not needed by the current project.

If you want to keep your system's gem repository clean you should consider using the --path option with bundle install. This will allow you to keep project dependencies outside of the system's gem repository.

Upvotes: 11

the Tin Man
the Tin Man

Reputation: 160551

If you're on *nix or Mac OS, you can put the names of the gems you want to remove in a text file. Then run this command:

xargs gem uninstall < path/to/text/file

xargs is a great tool for processing long lists of files. In this case, it takes the contents of the text file, when its piped in via STDIN, and puts each line read into the command-line of gem uninstall. It will continue to do that until the text file is exhausted.

Upvotes: 3

Related Questions