Jesse and I are shooting for the minimal viable fix here, with the idea that
a great deal of refactoring is needed but isn't appropriate at this time.  The
changes in this commit are:

* Index the function-holding modules by environment

* Pass the evaluation environment into the function retrieving methods (which
we can get from the scope)

* We need to know the "current environment" when we're defining a function so
we can attach it to the proper module, and this information isn't dynamically
available when user-defined functions are being created (we're being called by
user written code that doesn't "know" about environments) so we cheat and
stash the value in Puppet::Node::Environment

* Add a special *root* environment for the built in functions, and extend all
scopes with it.

* Index the function characteristics (name, type, docstring, etc.) by 
environment

* Make the autoloader environment aware, so that it uses the modulepath for the
specified environment rather than the default

* Turn off caching of the modulepath since it potentially changes for each node

Signed-off-by: Markus Roberts <[email protected]>
---
 lib/puppet/dsl/resource_api.rb    |    4 +-
 lib/puppet/node/environment.rb    |   12 +++++++++
 lib/puppet/parser/ast/function.rb |    7 ++---
 lib/puppet/parser/compiler.rb     |    1 +
 lib/puppet/parser/functions.rb    |   49 ++++++++++++++----------------------
 lib/puppet/parser/scope.rb        |    1 +
 lib/puppet/parser/type_loader.rb  |    2 +-
 lib/puppet/util/autoload.rb       |   19 ++++++--------
 8 files changed, 47 insertions(+), 48 deletions(-)

diff --git a/lib/puppet/dsl/resource_api.rb b/lib/puppet/dsl/resource_api.rb
index 98e5215..ffe7777 100644
--- a/lib/puppet/dsl/resource_api.rb
+++ b/lib/puppet/dsl/resource_api.rb
@@ -33,7 +33,7 @@ class Puppet::DSL::ResourceAPI
 
         name = map_function(name)
 
-        return call_function(name, args) if 
Puppet::Parser::Functions.function(name)
+        return call_function(name, args) if 
Puppet::Parser::Functions.function(name,environment)
 
         super
     ensure
@@ -66,7 +66,7 @@ class Puppet::DSL::ResourceAPI
     end
 
     def call_function(name, args)
-        return false unless method = Puppet::Parser::Functions.function(name)
+        return false unless method = 
Puppet::Parser::Functions.function(name,environment)
         scope.send(method, *args)
     end
 
diff --git a/lib/puppet/node/environment.rb b/lib/puppet/node/environment.rb
index d1a126a..4363eea 100644
--- a/lib/puppet/node/environment.rb
+++ b/lib/puppet/node/environment.rb
@@ -41,6 +41,18 @@ class Puppet::Node::Environment
         @seen[symbol] = obj
     end
 
+    def self.current
+        @current || root
+    end
+
+    def self.current=(env)
+        @current = new(env)
+    end
+
+    def self.root
+        @root ||= new(:'*root*') 
+    end
+
     # This is only used for testing.
     def self.clear
         @seen.clear
diff --git a/lib/puppet/parser/ast/function.rb 
b/lib/puppet/parser/ast/function.rb
index 4c3a5dc..59cdc76 100644
--- a/lib/puppet/parser/ast/function.rb
+++ b/lib/puppet/parser/ast/function.rb
@@ -13,19 +13,19 @@ class Puppet::Parser::AST
         def evaluate(scope)
 
             # Make sure it's a defined function
-            unless @fname
+            unless @fname = 
Puppet::Parser::Functions.function(@name,scope.environment)
                 raise Puppet::ParseError, "Unknown function %s" % @name
             end
 
             # Now check that it's been used correctly
             case @ftype
             when :rvalue
-                unless Puppet::Parser::Functions.rvalue?(@name)
+                unless 
Puppet::Parser::Functions.rvalue?(@name,scope.environment)
                     raise Puppet::ParseError, "Function '%s' does not return a 
value" %
                         @name
                 end
             when :statement
-                if Puppet::Parser::Functions.rvalue?(@name)
+                if Puppet::Parser::Functions.rvalue?(@name,scope.environment)
                     raise Puppet::ParseError,
                         "Function '%s' must be the value of a statement" %
                         @name
@@ -48,7 +48,6 @@ class Puppet::Parser::AST
 
             super(hash)
 
-             @fname = Puppet::Parser::Functions.function(@name)
             # Lastly, check the parity
         end
 
diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb
index beba438..22e2f07 100644
--- a/lib/puppet/parser/compiler.rb
+++ b/lib/puppet/parser/compiler.rb
@@ -127,6 +127,7 @@ class Puppet::Parser::Compiler
                 @environment = nil
             end
         end
+        Puppet::Node::Environment.current = @environment
         @environment
     end
 
diff --git a/lib/puppet/parser/functions.rb b/lib/puppet/parser/functions.rb
index 6ba3669..9cdbf12 100644
--- a/lib/puppet/parser/functions.rb
+++ b/lib/puppet/parser/functions.rb
@@ -6,7 +6,7 @@ require 'puppet/parser/scope'
 # class.
 module Puppet::Parser::Functions
 
-    @functions = {}
+    @functions = Hash.new { |h,k| h[k] = {} }
     @modules = {}
 
     class << self
@@ -24,15 +24,17 @@ module Puppet::Parser::Functions
         @autoloader
     end
 
+    Environment = Puppet::Node::Environment
+
     def self.environment_module(env = nil)
-        @module ||= Module.new
+        @modules[ env || Environment.current || Environment.root ] ||= 
Module.new
     end
 
     # Create a new function type.
     def self.newfunction(name, options = {}, &block)
         name = symbolize(name)
 
-        if @functions.include? name
+        if functions.include?(name)
             raise Puppet::DevError, "Function %s already defined" % name
         end
 
@@ -46,10 +48,10 @@ module Puppet::Parser::Functions
         environment_module.send(:define_method, fname, &block)
 
         # Someday we'll support specifying an arity, but for now, nope
-        #...@functions[name] = {:arity => arity, :type => ftype}
-        @functions[name] = {:type => ftype, :name => fname}
+        #functions[name] = {:arity => arity, :type => ftype}
+        functions[name] = {:type => ftype, :name => fname}
         if options[:doc]
-            @functions[name][:doc] = options[:doc]
+            functions[name][:doc] = options[:doc]
         end
     end
 
@@ -57,29 +59,25 @@ module Puppet::Parser::Functions
     def self.rmfunction(name)
         name = symbolize(name)
 
-        unless @functions.include? name
+        unless functions.include? name
             raise Puppet::DevError, "Function %s is not defined" % name
         end
 
-        @functions.delete(name)
+        functions.delete name
 
         fname = "function_" + name.to_s
         environment_module.send(:remove_method, fname)
     end
 
     # Determine if a given name is a function
-    def self.function(name)
+    def self.function(name,environment)
         name = symbolize(name)
 
-        unless @functions.include? name
-            autoloader.load(name)
+        unless functions.include?(name) or 
functions(Puppet::Node::Environment.root).include?(name)
+            autoloader.load(name,environment)
         end
 
-        if @functions.include? name
-            return @functions[name][:name]
-        else
-            return false
-        end
+        ( functions(Puppet::Node::Environment.root)[name] || functions[name] 
|| {:name => false} )[:name]
     end
 
     def self.functiondocs
@@ -87,7 +85,7 @@ module Puppet::Parser::Functions
 
         ret = ""
 
-        @functions.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, hash|
+        functions.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, hash|
             #ret += "%s\n%s\n" % [name, hash[:type]]
             ret += "%s\n%s\n" % [name, "-" * name.to_s.length]
             if hash[:doc]
@@ -102,22 +100,13 @@ module Puppet::Parser::Functions
         return ret
     end
 
-    def self.functions
-        @functions.keys
+    def self.functions(env = nil)
+        @functions[ env || Environment.current || Environment.root ]
     end
 
     # Determine if a given function returns a value or not.
-    def self.rvalue?(name)
-        name = symbolize(name)
-
-        if @functions.include? name
-            case @functions[name][:type]
-            when :statement; return false
-            when :rvalue; return true
-            end
-        else
-            return false
-        end
+    def self.rvalue?(name,env)
+        (functions(env)[symbolize(name)] || {})[:type] == :rvalue
     end
 
     # Runs a newfunction to create a function for each of the log levels
diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb
index 991e123..a097d2a 100644
--- a/lib/puppet/parser/scope.rb
+++ b/lib/puppet/parser/scope.rb
@@ -505,6 +505,7 @@ class Puppet::Parser::Scope
     private
 
     def extend_with_functions_module
+        extend 
Puppet::Parser::Functions.environment_module(Puppet::Node::Environment.root)
         extend Puppet::Parser::Functions.environment_module(compiler ? 
environment : nil)
     end
 end
diff --git a/lib/puppet/parser/type_loader.rb b/lib/puppet/parser/type_loader.rb
index 3268eae..bcb7fa3 100644
--- a/lib/puppet/parser/type_loader.rb
+++ b/lib/puppet/parser/type_loader.rb
@@ -114,7 +114,7 @@ class Puppet::Parser::TypeLoader
     end
 
     def parse_file(file)
-        Puppet.debug("importing '#{file}'")
+        Puppet.debug("importing '#{file}' in environment #{environment}")
         parser = Puppet::Parser::Parser.new(environment)
         parser.file = file
         parser.parse
diff --git a/lib/puppet/util/autoload.rb b/lib/puppet/util/autoload.rb
index 7358618..4a687bf 100644
--- a/lib/puppet/util/autoload.rb
+++ b/lib/puppet/util/autoload.rb
@@ -73,12 +73,12 @@ class Puppet::Util::Autoload
 
     # Load a single plugin by name.  We use 'load' here so we can reload a
     # given plugin.
-    def load(name)
+    def load(name,env=nil)
         return false if named_file_missing?(name)
 
         path = name.to_s + ".rb"
 
-        searchpath.each do |dir|
+        searchpath(env).each do |dir|
             file = File.join(dir, path)
             next unless file_exist?(file)
             begin
@@ -130,25 +130,22 @@ class Puppet::Util::Autoload
     end
 
     # The list of directories to search through for loadable plugins.
-    # We have to hard-code the ttl because this library is used by
-    # so many other classes it's hard to get the load-order such that
-    # the defaults load before this.
-    cached_attr(:searchpath, :ttl => 15) do
-        search_directories.collect { |d| File.join(d, @path) }.find_all { |d| 
FileTest.directory?(d) }
+    def searchpath(env=nil)
+        search_directories(env).collect { |d| File.join(d, @path) }.find_all { 
|d| FileTest.directory?(d) }
     end
 
-    def module_directories
+    def module_directories(env=nil)
         # We have to require this late in the process because otherwise we 
might have
         # load order issues.
         require 'puppet/node/environment'
-        Puppet::Node::Environment.new.modulepath.collect do |dir|
+        Puppet::Node::Environment.new(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(dummy_argument=:work_arround_for_ruby_GC_bug)
-        [module_directories, Puppet[:libdir].split(File::PATH_SEPARATOR), 
$:].flatten
+    def search_directories(env=nil)
+        [module_directories(env), Puppet[:libdir].split(File::PATH_SEPARATOR), 
$:].flatten
     end
 end
-- 
1.6.4

-- 
You received this message because you are subscribed to the Google Groups 
"Puppet Developers" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/puppet-dev?hl=en.

Reply via email to