Basic logic behind prune action is good.
This commit is contained in:
parent
f257d1211f
commit
472d4182c1
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue