EsoErik

Sunday, July 26, 2009

 

cleaning up obsolete gems included with Ruby on OS X

OS X 10.5.7 includes ruby 1.8.6 with a number of outdated gems. When updating, the gem command line program installs newer gems alongside the outdated ones. To tidy up, gem supports the "cleanup" command; this would normally blow away all gems that are obsoleted by newer installed gems. However, because the gems included with OS X are installed to a path that is not the default installation location for new gems, "cleanup" fails.

In order to work around this, I wrote a ruby script that:
On my laptop, this uninstalled all the obsolete gems, just as "gem cleanup" would have if it had worked correctly. A caveat: I call "gem uninstall" with the no executables parameter as the bin directory in the Apple gem directory did not exist for me. Newer gems replace the frontend scripts to these binaries, and I don't know where the old binaries actually dwelt. Hopefully they were in directories of the uninstalled gems and were therefore deleted. If not, they are evidentally not in the default shell path and merely waste a little disk space.

#! /usr/bin/env ruby
#
# (c) Erik Hvatum 2009
#

require 'rubygems'
require 'optparse'
require 'open4'

#* Parse command line options

options = {}
optparser = OptionParser.new
optparser.banner = "Usage: #{$0} [options]"

options[:dryrun] = false
optparser.on('-d', '--dry', 'Dry run; print the calls to gem that would be executed') do
options[:dryrun] = true
end

options[:gemexec] = 'gem'
optparser.on('-g', '--gem EXECUTABLE', 'Specify gem executable name; ie, gem19 or /usr/local/bin/gem19') do |gem|
options[:gemexec] = gem
end

optparser.on('-h', '--help', 'Display help information') do
puts optparser
exit
end

options[:verbose] = false
optparser.on('-v', '--verbose', 'Output more information') do
options[:verbose] = true
end

begin
optparser.parse! ARGV
rescue OptionParser::ParseError => e
abort e
end

if ARGV.size > 0
abort "invalid option: #{ARGV[0]}"
end

#* Make list of outdated gems to remove

GemInfo = Struct.new(:nameAndVersion, :name, :version, :path)
gemsToUninstall = []

gemCleanDryCmd = "#{options[:gemexec]} cleanup -d"
if options[:verbose]
puts "To determine which gems are outdated and should be removed, executing shell command: #{gemCleanDryCmd}"
end

pid, stdin, stdout, stderr = Open4::popen4(gemCleanDryCmd)
ignored, status = Process::waitpid2(pid)
if status != 0
abort "Error: #{gemCleanDryCmd} returned #{status} instead of zero, with stdout output\n#{stdout.read.strip}\nand stderr output\n#{stderr.read.strip}"
end

l = stdout.readline()
unless l =~ /^\s*Cleaning up installed gems\.\.\.\s*$/i
abort "Error: First line of #{gemCleanDryCmd} stdout is not \"Cleaning up installed gems...\""
end

while 1
l = stdout.readline()

if l == nil
abort "Error: Premature end of output from #{gemCleanDryCmd}"
end

if l =~ /^\s*Clean Up Complete\s*$/i
break
end

if l =~ /^\s*Dry Run Mode: Would uninstall (\S+)\s*$/i
if options[:verbose]
puts "Found obsolete gem to uninstall: #{$1}"
end
gemsToUninstall << GemInfo.new($1)
else
abort "Error: Failed to parse output from #{gemCleanDryCmd}: #{l}"
end
end

for gem in gemsToUninstall
if gem.nameAndVersion =~ /^(.*)-((?:\d+\.?)+)$/
gem.name, gem.version = $1, $2
else
abort "Error: Failed to parse gem name: #{gem.nameAndVersion}"
end
end

#* Get paths of identified gems

for gem in gemsToUninstall
cmd = "#{options[:gemexec]} list -d -l #{gem.name}"
if options[:verbose]
puts cmd
end
pid, stdin, stdout, stderr = Open4::popen4(cmd)
ignored, status = Process::waitpid2(pid)
if status != 0
abort "Error: #{cmd} returned #{status} instead of zero, with stdout output\n#{stdout.read.strip}\nand stderr output\n#{stderr.read.strip}"
end
o = stdout.read()
unless o =~ /Installed at /
abort "Error: failed to parse output of #{cmd}"
end
while $' =~ /\s*\(((?:\d+\.?)+)\): (.*)$/
if $1 == gem.version
gem.path = $2
break
end
end
end

#* Perform removal

for gem in gemsToUninstall
cmd = "#{options[:gemexec]} uninstall -I --no-executables
--install-dir #{gem.path} --version \"=#{gem.version}\" #{gem.name}"
if options[:dryrun] || options[:verbose]
puts cmd
end
unless options[:dryrun]
system(cmd)
end
end

Labels: ,


Comments:

Post a Comment

Subscribe to Post Comments [Atom]





<< Home

Archives

July 2009   August 2009   September 2009   October 2009   November 2009   December 2009   January 2010   September 2010   December 2010   January 2011   February 2011   April 2011   June 2011   August 2011   February 2012   June 2012   July 2012   August 2012   October 2012   November 2012   January 2014   April 2014   June 2014   August 2014   September 2014   October 2014   January 2015   March 2015   April 2015   June 2015   November 2015   December 2015   January 2016   June 2016   August 2016   January 2017   March 2017   April 2018   April 2019   June 2019   January 2020  

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]