Basic logic behind prune action is good.

This commit is contained in:
Mitchell Hashimoto 2013-02-03 10:15:46 -08:00
parent f257d1211f
commit 472d4182c1
3 changed files with 142 additions and 3 deletions

View File

@ -10,6 +10,7 @@ module VagrantPlugins
Vagrant::Action::Builder.new.tap do |b|
b.use BundlerCheck
b.use InstallGem
b.use PruneGems
end
end
@ -26,6 +27,7 @@ module VagrantPlugins
autoload :BundlerCheck, action_root.join("bundler_check")
autoload :InstallGem, action_root.join("install_gem")
autoload :ListPlugins, action_root.join("list_plugins")
autoload :PruneGems, action_root.join("prune_gems")
end
end
end

View File

@ -0,0 +1,131 @@
require "rubygems"
require "set"
require "log4r"
module VagrantPlugins
module CommandPlugin
module Action
# This class prunes any unnecessary gems from the Vagrant-managed
# gem folder. This keeps the gem folder to the absolute minimum set
# of required gems and doesn't let it blow up out of control.
#
# A high-level description of how this works:
#
# 1. Get the list of installed plugins. Vagrant maintains this
# list on its own.
# 2. Get the list of installed RubyGems.
# 3. Find the latest version of each RubyGem that matches an installed
# plugin. These are our root RubyGems that must be installed.
# 4. Go through each root and mark all dependencies recursively as
# necessary.
# 5. Set subtraction between all gems and necessary gems yields a
# list of gems that aren't needed. Uninstall them.
#
class PruneGems
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new("vagrant::plugins::plugincommand::prune")
end
def call(env)
@logger.info("Pruning gems...")
# Get the list of installed plugins according to the state file
installed = Set.new(env[:plugin_state_file].installed_plugins)
# Get the actual specifications of installed gems
all_specs = env[:gem_helper].with_environment do
result = []
Gem::Specification.find_all { |s| result << s }
result
end
# The list of specs to prune initially starts out as all of them
all_specs = Set.new(all_specs)
# Go through each spec and find the latest version of the installed
# gems, since we want to keep those.
installed_specs = {}
@logger.debug("Collecting installed plugin gems...")
all_specs.each do |spec|
# If this isn't a spec that we claim is installed, skip it
next if !installed.include?(spec.name)
# If it is already in the specs, then we need to make sure we
# have the latest version.
if installed_specs.has_key?(spec.name)
if installed_specs[spec.name].version > spec.version
next
end
end
@logger.debug(" -- #{spec.name} (#{spec.version})")
installed_specs[spec.name] = spec
end
# Recursive dependency checker to keep all dependencies and remove
# all non-crucial gems from the prune list.
good_specs = Set.new
to_check = installed_specs.values
while true
# If we're out of gems to check then we break out
break if to_check.empty?
# Get a random (first) element to check
spec = to_check.shift
# If we already checked this, then do the next one
next if good_specs.include?(spec)
# Find all the dependencies and add the latest compliant gem
# to the `to_check` list.
if spec.dependencies.length > 0
@logger.debug("Finding dependencies for '#{spec.name}' to mark as good...")
spec.dependencies.each do |dep|
# Ignore non-runtime dependencies
next if dep.type != :runtime
@logger.debug("Searching for: '#{dep.name}'")
latest_matching = nil
all_specs.each do |prune_spec|
if dep =~ prune_spec
# If we have a matching one already and this one isn't newer
# then we ditch it.
next if latest_matching &&
prune_spec.version <= latest_matching.version
latest_matching = prune_spec
end
end
if latest_matching.nil?
@logger.error("Missing dependency for '#{spec.name}': #{dep.name}")
next
end
@logger.debug("Latest matching dep: '#{latest_matching.name}' (#{latest_matching.version})")
to_check << latest_matching
end
end
# Add ito the list of checked things so we don't accidentally
# re-check it
good_specs.add(spec)
end
# Figure out the gems we need to prune
prune_specs = all_specs - good_specs
@logger.info("Gems to prune: #{prune_specs.inspect}")
# TODO: Prune
@app.call(env)
end
end
end
end
end

View File

@ -49,16 +49,22 @@ module VagrantPlugins
# path.
def with_environment
old_gem_home = ENV["GEM_HOME"]
old_gem_path = ENV["GEM_PATH"]
ENV["GEM_HOME"] = @gem_home
@logger.debug("Set GEM_HOME to: #{ENV["GEM_HOME"]}")
ENV["GEM_PATH"] = @gem_home
@logger.debug("Set GEM_* to: #{ENV["GEM_HOME"]}")
# Clear paths so that it reads the new GEM_HOME setting
Gem.clear_paths
Gem.paths = ENV
return yield
ensure
# Restore the old GEM_HOME
# Restore the old GEM_* settings
ENV["GEM_HOME"] = old_gem_home
ENV["GEM_PATH"] = old_gem_path
# Reset everything
Gem.paths = ENV
end
end
end