require 'puppet/util/warnings' # Autoload paths, either based on names or all at once. class Puppet::Util::Autoload require 'puppet/util/autoload/file_cache' include Puppet::Util include Puppet::Util::Warnings include Puppet::Util::Autoload::FileCache @autoloaders = {} @loaded = [] class << self attr_reader :autoloaders private :autoloaders end # Send [], []=, and :clear to the @autloaders hash Puppet::Util.classproxy self, :autoloaders, "[]", "[]=" # List all loaded files. def self.list_loaded @loaded.sort { |a,b| a[0] <=> b[0] }.collect do |path, hash| "#{path}: #{hash[:file]}" end end # Has a given path been loaded? This is used for testing whether a # changed file should be loaded or just ignored. This is only # used in network/client/master, when downloading plugins, to # see if a given plugin is currently loaded and thus should be # reloaded. def self.loaded?(path) path = path.to_s.sub(/\.rb$/, '') @loaded.include?(path) end # Save the fact that a given path has been loaded. This is so # we can load downloaded plugins if they've already been loaded # into memory. def self.loaded(file) $" << file + ".rb" unless $".include?(file) @loaded << file unless @loaded.include?(file) end attr_accessor :object, :path, :objwarn, :wrap def initialize(obj, path, options = {}) @path = path.to_s raise ArgumentError, "Autoload paths cannot be fully qualified" if absolute_path?(@path) @object = obj self.class[obj] = self options.each do |opt, value| opt = opt.intern if opt.is_a? String begin self.send(opt.to_s + "=", value) rescue NoMethodError raise ArgumentError, "#{opt} is not a valid option" end end @wrap = true unless defined?(@wrap) end # Load a single plugin by name. We use 'load' here so we can reload a # given plugin. def load(name,env=nil) path = name.to_s + ".rb" searchpath(env).each do |dir| file = File.join(dir, path) next unless file_exist?(file) begin Kernel.load file, @wrap name = name.intern loaded name, file return true rescue SystemExit,NoMemoryError raise rescue Exception => detail puts detail.backtrace if Puppet[:trace] raise Puppet::Error, "Could not autoload #{name}: #{detail}" end end false end # Mark the named object as loaded. Note that this supports unqualified # queries, while we store the result as a qualified query in the class. def loaded(name, file) self.class.loaded(File.join(@path, name.to_s)) end # Indicate whether the specfied plugin has been loaded. def loaded?(name) self.class.loaded?(File.join(@path, name.to_s)) end # Load all instances that we can. This uses require, rather than load, # so that already-loaded files don't get reloaded unnecessarily. def loadall # Load every instance of everything we can find. files_to_load.each do |file| name = File.basename(file).chomp(".rb").intern next if loaded?(name) begin loaded(name, file) Kernel.require file rescue SystemExit,NoMemoryError raise rescue Exception => detail puts detail.backtrace if Puppet[:trace] raise Puppet::Error, "Could not autoload #{file}: #{detail}" end end end def files_to_load searchpath.map { |dir| Dir.glob("#{dir}/*.rb") }.flatten end # The list of directories to search through for loadable plugins. def searchpath(env=nil) search_directories(env).uniq.collect { |d| File.join(d, @path) }.find_all { |d| FileTest.directory?(d) } end def module_directories(env=nil) # We have to require this late in the process because otherwise we might # have load order issues. Since require is much slower than defined?, we # can skip that - and save some 2,155 invocations of require in my real # world testing. --daniel 2012-07-10 require 'puppet/node/environment' unless defined?(Puppet::Node::Environment) real_env = Puppet::Node::Environment.new(env) # We're using a per-thread cache of said module directories, so that # we don't scan the filesystem each time we try to load something with # this autoload instance. But since we don't want to cache for the eternity # this env_module_directories gets reset after the compilation on the master. # This is also reset after an agent ran. # One of the side effect of this change is that this module directories list will be # shared among all autoload that we have running at a time. But that won't be an issue # as by definition those directories are shared by all autoload. Thread.current[:env_module_directories] ||= {} Thread.current[:env_module_directories][real_env] ||= real_env.modulepath.collect do |dir| Dir.entries(dir).reject { |f| f =~ /^\./ }.collect { |f| File.join(dir, f) } end.flatten.collect { |d| [File.join(d, "plugins"), File.join(d, "lib")] }.flatten.find_all do |d| FileTest.directory?(d) end end def search_directories(env=nil) [module_directories(env), Puppet[:libdir].split(File::PATH_SEPARATOR), $LOAD_PATH].flatten end end