Pass the makefile while expanding expansions and functions. This is preparation for implementing $(eval) and eliminating chdir(), which is a prerequisite for running recursive make in a single process.
authorBenjamin Smedberg <benjamin@smedbergs.us>
Sat, 14 Feb 2009 21:15:05 -0500
changeset 120 0d43efb31b37
parent 118 7cbb1d534408
child 121 ace16e634043
push id66
push userbsmedberg@mozilla.com
push date2009-02-15 02:16 +0000
Pass the makefile while expanding expansions and functions. This is preparation for implementing $(eval) and eliminating chdir(), which is a prerequisite for running recursive make in a single process.
pymake/data.py
pymake/functions.py
pymake/parser.py
--- a/pymake/data.py
+++ b/pymake/data.py
@@ -118,28 +118,30 @@ class Expansion(object):
             assert len(self) == 1 or not isinstance(self[-2], str), "Strings didn't fold"
             self[-1] = self[-1].rstrip()
 
     def trimlastnewline(self):
         """Strip only the last newline, if present."""
         if len(self) > 0 and isinstance(self[-1], str) and self[-1][-1] == '\n':
             self[-1] = self[-1][:-1]
 
-    def resolve(self, variables, setting=[]):
+    def resolve(self, makefile, variables, setting=[]):
         """
         Resolve this variable into a value, by interpolating the value
         of other variables.
 
         @param setting (Variable instance) the variable currently
                being set, if any. Setting variables must avoid self-referential
                loops.
         """
+        assert isinstance(makefile, Makefile)
+        assert isinstance(variables, Variables)
         assert isinstance(setting, list)
 
-        return ''.join( (_if_else(isinstance(i, str), lambda: i, lambda: i.resolve(variables, setting))
+        return ''.join( (_if_else(isinstance(i, str), lambda: i, lambda: i.resolve(makefile, variables, setting))
                          for i in self._elements) )
 
     def __len__(self):
         return len(self._elements)
 
     def __getitem__(self, key):
         return self._elements[key]
 
@@ -240,31 +242,31 @@ class Variables(object):
         prevflavor, prevsource, prevvalue = self.get(name)
         if prevsource is not None and source > prevsource:
             # TODO: give a location for this warning
             log.warning("not setting variable '%s', set by higher-priority source to value '%s'" % (name, prevvalue))
             return
 
         self._map[name] = (flavor, source, value)
 
-    def append(self, name, source, value, variables):
+    def append(self, name, source, value, variables, makefile):
         assert source in (self.SOURCE_OVERRIDE, self.SOURCE_MAKEFILE, self.SOURCE_AUTOMATIC)
         assert isinstance(value, str)
         
         if name in self._map:
             prevflavor, prevsource, prevvalue = self._map[name]
             if source > prevsource:
                 # TODO: log a warning?
                 return
 
             if prevflavor == self.FLAVOR_SIMPLE:
                 d = pymake.parser.Data(None, None)
                 d.append(value, pymake.parser.Location("Expansion of variable '%s'" % (name,), 1, 0))
                 e, t, o = pymake.parser.parsemakesyntax(d, 0, (), pymake.parser.iterdata)
-                val = e.resolve(variables, [name])
+                val = e.resolve(makefile, variables, [name])
             else:
                 val = value
 
             self._map[name] = prevflavor, prevsource, prevvalue + ' ' + val
         else:
             self._map[name] = self.FLAVOR_APPEND, source, value
 
     def merge(self, other):
@@ -601,17 +603,17 @@ class Target(object):
             self.vpathtarget = self.target
             self.mtime = None
             return
 
         if self.target.startswith('-l'):
             stem = self.target[2:]
             f, s, e = makefile.variables.get('.LIBPATTERNS')
             if e is not None:
-                libpatterns = map(Pattern, splitwords(e.resolve(makefile.variables, [])))
+                libpatterns = map(Pattern, splitwords(e.resolve(makefile, makefile.variables)))
                 if len(libpatterns):
                     searchdirs = [''] + makefile.vpath
 
                     for lp in libpatterns:
                         if not lp.ispattern():
                             raise DataError('.LIBPATTERNS contains a non-pattern')
 
                         libname = lp.resolve('', stem)
@@ -820,17 +822,17 @@ class Rule(object):
 
         v = Variables(parent=target.variables)
         setautomaticvariables(v, makefile, target, self.prerequisites)
         # TODO: $* in non-pattern rules sucks
 
         env = makefile.getsubenvironment(v)
 
         for c in self.commands:
-            cstring = c.resolve(v)
+            cstring = c.resolve(makefile, v)
             for cline in splitcommand(cstring):
                 cline, isHidden, isRecursive, ignoreErrors = findmodifiers(cline)
                 if not len(cline) or cline.isspace():
                     continue
                 if not isHidden:
                     print "%s $ %s" % (c.loc, cline)
                 r = subprocess.call(cline, shell=True, env=env)
                 if r != 0 and not ignoreErrors:
@@ -922,17 +924,17 @@ class PatternRule(object):
         v = Variables(parent=target.variables)
         setautomaticvariables(v, makefile, target,
                               self.prerequisitesforstem(dir, stem))
         setautomatic(v, '*', [dir + stem])
 
         env = makefile.getsubenvironment(v)
 
         for c in self.commands:
-            cstring = c.resolve(v)
+            cstring = c.resolve(makefile, v)
             for cline in splitcommand(cstring):
                 cline, isHidden, isRecursive, ignoreErrors = findmodifiers(cline)
                 if not len(cline) or cline.isspace():
                     continue
                 if not isHidden:
                     print "%s $ %s" % (c.loc, cline)
                 r = subprocess.call(cline, shell=True, env=env)
                 if r != 0 and not ignoreErrors:
@@ -1032,40 +1034,40 @@ class Makefile(object):
         Various activities, such as "eval", are not allowed after parsing is
         finished. In addition, various warnings and errors can only be issued
         after the parsing data model is complete. All dependency resolution
         and rule execution requires that parsing be finished.
         """
         self.parsingfinished = True
 
         flavor, source, value = self.variables.get('GPATH')
-        if value is not None and value.resolve(self.variables, ['GPATH']).strip() != '':
+        if value is not None and value.resolve(self, self.variables, ['GPATH']).strip() != '':
             raise DataError('GPATH was set: pymake does not support GPATH semantics')
 
         flavor, source, value = self.variables.get('VPATH')
         if value is None:
             self.vpath = []
         else:
-            self.vpath = filter(lambda e: e != '', re.split('[:\s]+', value.resolve(self.variables, ['VPATH'])))
+            self.vpath = filter(lambda e: e != '', re.split('[:\s]+', value.resolve(self, self.variables, ['VPATH'])))
 
         targets = list(self._targets.itervalues())
         for t in targets:
             t.explicit = True
             for r in t.rules:
                 for p in r.prerequisites:
                     self.gettarget(p).explicit = True
 
     def include(self, path, required=True, loc=None):
         """
         Include the makefile at `path`.
         """
         self.included.append(path)
         if os.path.exists(path):
             fd = open(path)
-            self.variables.append('MAKEFILE_LIST', Variables.SOURCE_AUTOMATIC, path, None)
+            self.variables.append('MAKEFILE_LIST', Variables.SOURCE_AUTOMATIC, path, None, self)
             pymake.parser.parsestream(fd, path, self)
             self.gettarget(path).explicit = True
         elif required:
             raise DataError("Attempting to include file '%s' which doesn't exist." % (path,), loc)
 
     def remakemakefiles(self):
         reparse = False
 
@@ -1085,24 +1087,24 @@ class Makefile(object):
 
     def getsubenvironment(self, variables):
         env = dict(os.environ)
         for vname in self.exportedvars:
             flavor, source, val = variables.get(vname)
             if val is None:
                 strval = ''
             else:
-                strval = val.resolve(variables, [vname])
+                strval = val.resolve(self, variables, [vname])
             env[vname] = strval
 
         makeflags = ''
 
         flavor, source, val = variables.get('MAKEFLAGS')
         if val is not None:
-            flagsval = val.resolve(variables, ['MAKEFLAGS'])
+            flagsval = val.resolve(self, variables, ['MAKEFLAGS'])
             if flagsval != '':
                 makeflags = flagsval
 
         makeflags += ' -- '
         makeflags += ' '.join((self.flagescape.sub(r'\\\1', o) for o in self.overrides))
 
         env['MAKEFLAGS'] = makeflags
 
--- a/pymake/functions.py
+++ b/pymake/functions.py
@@ -10,17 +10,17 @@ log = data.log
 class Function(object):
     """
     An object that represents a function call. This class is always subclassed
     with the following methods and attributes:
 
     minargs = minimum # of arguments
     maxargs = maximum # of arguments (0 means unlimited)
 
-    def resolve(self, variables, setting)
+    def resolve(self, makefile, variables, setting)
         Calls the function
         @returns string
     """
     def __init__(self, loc):
         self._arguments = []
         self.loc = loc
         assert self.minargs > 0
 
@@ -46,213 +46,213 @@ class VariableRef(Function):
     def __init__(self, loc, vname):
         self.loc = loc
         assert isinstance(vname, data.Expansion)
         self.vname = vname
         
     def setup(self):
         assert False, "Shouldn't get here"
 
-    def resolve(self, variables, setting):
-        vname = self.vname.resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        vname = self.vname.resolve(makefile, variables, setting)
         if vname in setting:
             raise data.DataError("Setting variable '%s' recursively references itself." % (vname,), self.loc)
 
         flavor, source, value = variables.get(vname)
         if value is None:
             log.debug("%s: variable '%s' was not set" % (self.loc, vname))
             return ''
 
-        return value.resolve(variables, setting + [vname])
+        return value.resolve(makefile, variables, setting + [vname])
 
 class SubstitutionRef(Function):
     """$(VARNAME:.c=.o) and $(VARNAME:%.c=%.o)"""
     def __init__(self, loc, varname, substfrom, substto):
         self.loc = loc
         self.vname = varname
         self.substfrom = substfrom
         self.substto = substto
 
     def setup(self):
         assert False, "Shouldn't get here"
 
-    def resolve(self, variables, setting):
-        vname = self.vname.resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        vname = self.vname.resolve(makefile, variables, setting)
         if vname in setting:
             raise data.DataError("Setting variable '%s' recursively references itself." % (vname,), self.loc)
 
-        substfrom = self.substfrom.resolve(variables, setting)
-        substto = self.substto.resolve(variables, setting)
+        substfrom = self.substfrom.resolve(makefile, variables, setting)
+        substto = self.substto.resolve(makefile, variables, setting)
 
         flavor, source, value = variables.get(vname)
         if value is None:
             log.debug("%s: variable '%s' was not set" % (self.loc, vname))
             return ''
 
-        evalue = value.resolve(variables, setting + [vname])
+        evalue = value.resolve(makefile, variables, setting + [vname])
         words = data.splitwords(evalue)
 
         f = data.Pattern(substfrom)
         if not f.ispattern():
             f = data.Pattern('%' + substfrom)
             substto = '%' + substto
 
         return " ".join((f.subst(substto, word, False)
                          for word in words))
 
 class SubstFunction(Function):
     name = 'subst'
     minargs = 3
     maxargs = 3
 
-    def resolve(self, variables, setting):
-        s = self._arguments[0].resolve(variables, setting)
-        r = self._arguments[1].resolve(variables, setting)
-        d = self._arguments[2].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        s = self._arguments[0].resolve(makefile, variables, setting)
+        r = self._arguments[1].resolve(makefile, variables, setting)
+        d = self._arguments[2].resolve(makefile, variables, setting)
         return d.replace(s, r)
 
 class PatSubstFunction(Function):
     name = 'patsubst'
     minargs = 3
     maxargs = 3
 
-    def resolve(self, variables, setting):
-        s = self._arguments[0].resolve(variables, setting)
-        r = self._arguments[1].resolve(variables, setting)
-        d = self._arguments[2].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        s = self._arguments[0].resolve(makefile, variables, setting)
+        r = self._arguments[1].resolve(makefile, variables, setting)
+        d = self._arguments[2].resolve(makefile, variables, setting)
 
         p = data.Pattern(s)
         return ' '.join((p.subst(r, word, False)
                          for word in data.splitwords(d)))
 
 class StripFunction(Function):
     name = 'strip'
     minargs = 1
     maxargs = 1
 
-    def resolve(self, variables, setting):
-        return ' '.join(data.splitwords(self._arguments[0].resolve(variables, setting)))
+    def resolve(self, makefile, variables, setting):
+        return ' '.join(data.splitwords(self._arguments[0].resolve(makefile, variables, setting)))
 
 class FindstringFunction(Function):
     name = 'findstring'
     minargs = 2
     maxargs = 2
 
-    def resolve(self, variables, setting):
-        s = self._arguments[0].resolve(variables, setting)
-        r = self._arguments[1].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        s = self._arguments[0].resolve(makefile, variables, setting)
+        r = self._arguments[1].resolve(makefile, variables, setting)
         if r.find(s) == -1:
             return ''
         return s
 
 class FilterFunction(Function):
     name = 'filter'
     minargs = 2
     maxargs = 2
 
-    def resolve(self, variables, setting):
-        ps = self._arguments[0].resolve(variables, setting)
-        d = self._arguments[1].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        ps = self._arguments[0].resolve(makefile, variables, setting)
+        d = self._arguments[1].resolve(makefile, variables, setting)
         plist = [data.Pattern(p) for p in data.splitwords(ps)]
         r = []
         for w in data.splitwords(d):
             if any((p.match(w) for p in plist)):
                     r.append(w)
                 
         return ' '.join(r)
 
 class FilteroutFunction(Function):
     name = 'filter-out'
     minargs = 2
     maxargs = 2
 
-    def resolve(self, variables, setting):
-        ps = self._arguments[0].resolve(variables, setting)
-        d = self._arguments[1].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        ps = self._arguments[0].resolve(makefile, variables, setting)
+        d = self._arguments[1].resolve(makefile, variables, setting)
         plist = [data.Pattern(p) for p in data.splitwords(ps)]
         r = []
         for w in data.splitwords(d):
             found = False
             if not any((p.match(w) for p in plist)):
                 r.append(w)
 
         return ' '.join(r)
 
 class SortFunction(Function):
     name = 'sort'
     minargs = 1
     maxargs = 1
 
-    def resolve(self, variables, setting):
-        d = self._arguments[0].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        d = self._arguments[0].resolve(makefile, variables, setting)
         w = data.splitwords(d)
         w.sort()
         return ' '.join((w for w in data.withoutdups(w)))
 
 class WordFunction(Function):
     name = 'word'
     minargs = 2
     maxargs = 2
 
-    def resolve(self, variables, setting):
-        n = self._arguments[0].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        n = self._arguments[0].resolve(makefile, variables, setting)
         # TODO: provide better error if this doesn't convert
         n = int(n)
-        words = data.splitwords(self._arguments[1].resolve(variables, setting))
+        words = data.splitwords(self._arguments[1].resolve(makefile, variables, setting))
         if n < 1 or n > len(words):
             return ''
         return words[n - 1]
 
 class WordlistFunction(Function):
     name = 'wordlist'
     minargs = 3
     maxargs = 3
 
-    def resolve(self, variables, setting):
-        nfrom = self._arguments[0].resolve(variables, setting)
-        nto = self._arguments[1].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        nfrom = self._arguments[0].resolve(makefile, variables, setting)
+        nto = self._arguments[1].resolve(makefile, variables, setting)
         # TODO: provide better errors if this doesn't convert
         nfrom = int(nfrom)
         nto = int(nto)
 
-        words = data.splitwords(self._arguments[2].resolve(variables, setting))
+        words = data.splitwords(self._arguments[2].resolve(makefile, variables, setting))
 
         if nfrom < 1:
             nfrom = 1
         if nto < 1:
             nto = 1
 
         return ' '.join(words[nfrom - 1:nto])
 
 class WordsFunction(Function):
     name = 'words'
     minargs = 1
     maxargs = 1
 
-    def resolve(self, variables, setting):
-        return str(len(data.splitwords(self._arguments[0].resolve(variables, setting))))
+    def resolve(self, makefile, variables, setting):
+        return str(len(data.splitwords(self._arguments[0].resolve(makefile, variables, setting))))
 
 class FirstWordFunction(Function):
     name = 'firstword'
     minargs = 1
     maxargs = 1
 
-    def resolve(self, variables, setting):
-        wl = data.splitwords(self._arguments[0].resolve(variables, setting))
+    def resolve(self, makefile, variables, setting):
+        wl = data.splitwords(self._arguments[0].resolve(makefile, variables, setting))
         if len(wl) == 0:
             return ''
         return wl[0]
 
 class LastWordFunction(Function):
     name = 'lastword'
     minargs = 1
     maxargs = 1
 
-    def resolve(self, variables, setting):
-        wl = data.splitwords(self._arguments[0].resolve(variables, setting))
+    def resolve(self, makefile, variables, setting):
+        wl = data.splitwords(self._arguments[0].resolve(makefile, variables, setting))
         if len(wl) == 0:
             return ''
         return wl[0]
 
 def pathsplit(path, default='./'):
     """
     Splits a path into dirpart, filepart on the last slash. If there is no slash, dirpart
     is ./
@@ -263,254 +263,254 @@ def pathsplit(path, default='./'):
 
     return dir + slash, file
 
 class DirFunction(Function):
     name = 'dir'
     minargs = 1
     maxargs = 1
 
-    def resolve(self, variables, setting):
+    def resolve(self, makefile, variables, setting):
         return ' '.join((pathsplit(path)[0]
-                         for path in data.splitwords(self._arguments[0].resolve(variables, setting))))
+                         for path in data.splitwords(self._arguments[0].resolve(makefile, variables, setting))))
 
 class NotDirFunction(Function):
     name = 'notdir'
     minargs = 1
     maxargs = 1
 
-    def resolve(self, variables, setting):
+    def resolve(self, makefile, variables, setting):
         return ' '.join((pathsplit(path)[1]
-                         for path in data.splitwords(self._arguments[0].resolve(variables, setting))))
+                         for path in data.splitwords(self._arguments[0].resolve(makefile, variables, setting))))
 
 class SuffixFunction(Function):
     name = 'suffix'
     minargs = 1
     maxargs = 1
 
     @staticmethod
     def suffixes(words):
         for w in words:
             dir, file = pathsplit(w)
             base, dot, suffix = file.rpartition('.')
             if base != '':
                 yield dot + suffix
 
-    def resolve(self, variables, setting):
-        return ' '.join(self.suffixes(data.splitwords(self._arguments[0].resolve(variables, setting))))
+    def resolve(self, makefile, variables, setting):
+        return ' '.join(self.suffixes(data.splitwords(self._arguments[0].resolve(makefile, variables, setting))))
 
 class BasenameFunction(Function):
     name = 'basename'
     minargs = 1
     maxargs = 1
 
     @staticmethod
     def basenames(words):
         for w in words:
             dir, file = pathsplit(w, '')
             base, dot, suffix = file.rpartition('.')
             if dot == '':
                 base = suffix
 
             yield dir + base
 
-    def resolve(self, variables, setting):
-        return ' '.join(self.basenames(data.splitwords(self._arguments[0].resolve(variables, setting))))
+    def resolve(self, makefile, variables, setting):
+        return ' '.join(self.basenames(data.splitwords(self._arguments[0].resolve(makefile, variables, setting))))
 
 class AddSuffixFunction(Function):
     name = 'addprefix'
     minargs = 2
     maxargs = 2
 
-    def resolve(self, variables, setting):
-        suffix = self._arguments[0].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        suffix = self._arguments[0].resolve(makefile, variables, setting)
 
-        return ' '.join((w + suffix for w in data.splitwords(self._arguments[1].resolve(variables, setting))))
+        return ' '.join((w + suffix for w in data.splitwords(self._arguments[1].resolve(makefile, variables, setting))))
 
 class AddPrefixFunction(Function):
     name = 'addsuffix'
     minargs = 2
     maxargs = 2
 
-    def resolve(self, variables, setting):
-        prefix = self._arguments[0].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        prefix = self._arguments[0].resolve(makefile, variables, setting)
 
-        return ' '.join((prefix + w for w in data.splitwords(self._arguments[1].resolve(variables, setting))))
+        return ' '.join((prefix + w for w in data.splitwords(self._arguments[1].resolve(makefile, variables, setting))))
 
 class JoinFunction(Function):
     name = 'join'
     minargs = 2
     maxargs = 2
 
     @staticmethod
     def iterjoin(l1, l2):
         for i in xrange(0, max(len(l1), len(l2))):
             i1 = i < len(l1) and l1[i] or ''
             i2 = i < len(l2) and l2[i] or ''
             yield i1 + i2
 
-    def resolve(self, variables, setting):
-        list1 = data.splitwords(self._arguments[0].resolve(variables, setting))
-        list2 = data.splitwords(self._arguments[1].resolve(variables, setting))
+    def resolve(self, makefile, variables, setting):
+        list1 = data.splitwords(self._arguments[0].resolve(makefile, variables, setting))
+        list2 = data.splitwords(self._arguments[1].resolve(makefile, variables, setting))
 
         return ' '.join(self.iterjoin(list1, list2))
 
 class WildcardFunction(Function):
     name = 'wildcard'
     minargs = 1
     maxargs = 1
 
-    def resolve(self, variables, setting):
+    def resolve(self, makefile, variables, setting):
         # TODO: will need work when we support -C without actually changing the OS cwd
-        pattern = self._arguments[0].resolve(variables, setting)
+        pattern = self._arguments[0].resolve(makefile, variables, setting)
         return ' '.join(glob.glob(pattern))
 
 class RealpathFunction(Function):
     name = 'realpath'
     minargs = 1
     maxargs = 1
 
-    def resolve(self, variables, setting):
+    def resolve(self, makefile, variables, setting):
         # TODO: will need work when we support -C without actually changing the OS cwd
         return ' '.join((os.path.realpath(f)
-                         for f in data.splitwords(self._arguments[0].resolve(variables, setting))))
+                         for f in data.splitwords(self._arguments[0].resolve(makefile, variables, setting))))
 
 class AbspathFunction(Function):
     name = 'abspath'
     minargs = 1
     maxargs = 1
 
-    def resolve(self, variables, setting):
+    def resolve(self, makefile, variables, setting):
         # TODO: will need work when we support -C without actually changing the OS cwd
         return ' '.join((os.path.abspath(f)
-                         for f in data.splitwords(self._arguments[0].resolve(variables, setting))))
+                         for f in data.splitwords(self._arguments[0].resolve(makefile, variables, setting))))
 
 class IfFunction(Function):
     name = 'if'
     minargs = 1
     maxargs = 3
 
     def setup(self):
         Function.setup(self)
         self._arguments[0].lstrip()
         self._arguments[0].rstrip()
 
-    def resolve(self, variables, setting):
-        condition = self._arguments[0].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        condition = self._arguments[0].resolve(makefile, variables, setting)
         if len(condition):
-            return self._arguments[1].resolve(variables, setting)
+            return self._arguments[1].resolve(makefile, variables, setting)
 
         if len(self._arguments) > 2:
-            return self._arguments[2].resolve(variables, setting)
+            return self._arguments[2].resolve(makefile, variables, setting)
 
         return ''
 
 class OrFunction(Function):
     name = 'or'
     minargs = 1
     maxargs = 0
 
-    def resolve(self, variables, setting):
+    def resolve(self, makefile, variables, setting):
         for arg in self._arguments:
-            r = arg.resolve(variables, setting)
+            r = arg.resolve(makefile, variables, setting)
             if r != '':
                 return r
 
         return ''
 
 class AndFunction(Function):
     name = 'and'
     minargs = 1
     maxargs = 0
 
-    def resolve(self, variables, setting):
+    def resolve(self, makefile, variables, setting):
         r = ''
 
         for arg in self._arguments:
-            r = arg.resolve(variables, setting)
+            r = arg.resolve(makefile, variables, setting)
             if r == '':
                 return ''
 
         return r
 
 class ForEachFunction(Function):
     name = 'foreach'
     minargs = 3
     maxargs = 3
 
-    def resolve(self, variables, setting):
-        vname = self._arguments[0].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        vname = self._arguments[0].resolve(makefile, variables, setting)
 
-        words = data.splitwords(self._arguments[1].resolve(variables, setting))
+        words = data.splitwords(self._arguments[1].resolve(makefile, variables, setting))
         e = self._arguments[2]
 
         results = []
 
         v = data.Variables(parent=variables)
         for w in words:
             v.set(vname, data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, w)
-            results.append(e.resolve(v, setting))
+            results.append(e.resolve(makefile, v, setting))
 
         return ' '.join(results)
 
 class CallFunction(Function):
     name = 'call'
     minargs = 1
     maxargs = 0
 
-    def resolve(self, variables, setting):
-        vname = self._arguments[0].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        vname = self._arguments[0].resolve(makefile, variables, setting)
         if vname in setting:
             raise data.DataError("Recursively setting variable '%s'" % (vname,))
 
         v = data.Variables(parent=variables)
         v.set('0', data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, vname)
         for i in xrange(1, len(self._arguments)):
-            param = self._arguments[i].resolve(variables, setting)
+            param = self._arguments[i].resolve(makefile, variables, setting)
             v.set(str(i), data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, param)
 
         flavor, source, e = variables.get(vname)
         if e is None:
             return ''
 
         if flavor == data.Variables.FLAVOR_SIMPLE:
             log.warning("%s: calling variable '%s' which is simply-expanded" % (self.loc, vname))
 
         # but we'll do it anyway
-        return e.resolve(v, setting + [vname])
+        return e.resolve(makefile, v, setting + [vname])
 
 class ValueFunction(Function):
     name = 'value'
     minargs = 1
     maxargs = 1
 
-    def resolve(self, variables, setting):
-        varname = self._arguments[0].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        varname = self._arguments[0].resolve(makefile, variables, setting)
 
         flavor, source, value = variables.get(varname, expand=False)
         if value is None:
             return ''
 
         return value
 
 class EvalFunction(Function):
     name = 'eval'
     minargs = 1
     maxargs = 1
 
-    def resolve(self, variables, setting):
+    def resolve(self, makefile, variables, setting):
         raise NotImplementedError('no eval yet')
 
 class OriginFunction(Function):
     name = 'origin'
     minargs = 1
     maxargs = 1
 
-    def resolve(self, variables, setting):
-        vname = self._arguments[0].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        vname = self._arguments[0].resolve(makefile, variables, setting)
 
         flavor, source, value = variables.get(vname)
         if source is None:
             return 'undefined'
 
         if source == data.Variables.SOURCE_OVERRIDE:
             return 'override'
 
@@ -528,18 +528,18 @@ class OriginFunction(Function):
 
         assert False, "Unexpected source value: %s" % source
 
 class FlavorFunction(Function):
     name = 'flavor'
     minargs = 1
     maxargs = 1
 
-    def resolve(self, variables, setting):
-        varname = self._arguments[0].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        varname = self._arguments[0].resolve(makefile, variables, setting)
         
         flavor, source, value = variables.get(varname)
         if flavor is None:
             return 'undefined'
 
         if flavor == data.Variables.FLAVOR_RECURSIVE:
             return 'recursive'
         elif flavor == data.Variables.FLAVOR_SIMPLE:
@@ -547,55 +547,55 @@ class FlavorFunction(Function):
 
         assert False, "Neither simple nor recursive?"
 
 class ShellFunction(Function):
     name = 'shell'
     minargs = 1
     maxargs = 1
 
-    def resolve(self, variables, setting):
-        cline = self._arguments[0].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        cline = self._arguments[0].resolve(makefile, variables, setting)
 
         p = subprocess.Popen(cline, shell=True, stdout=subprocess.PIPE)
         stdout, stderr = p.communicate()
 
         stdout = stdout.replace('\r\n', '\n')
         if len(stdout) > 1 and stdout[-1] == '\n':
             stdout = stdout[:-1]
         stdout = stdout.replace('\n', ' ')
 
         return stdout
 
 class ErrorFunction(Function):
     name = 'error'
     minargs = 1
     maxargs = 1
 
-    def resolve(self, variables, setting):
-        v = self._arguments[0].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        v = self._arguments[0].resolve(makefile, variables, setting)
         raise data.DataError(v, self.loc)
 
 class WarningFunction(Function):
     name = 'warning'
     minargs = 1
     maxargs = 1
 
-    def resolve(self, variables, setting):
-        v = self._arguments[0].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        v = self._arguments[0].resolve(makefile, variables, setting)
         log.warning(v)
         return ''
 
 class InfoFunction(Function):
     name = 'info'
     minargs = 1
     maxargs = 1
 
-    def resolve(self, variables, setting):
-        v = self._arguments[0].resolve(variables, setting)
+    def resolve(self, makefile, variables, setting):
+        v = self._arguments[0].resolve(makefile, variables, setting)
         log.info(v)
         return ''
 
 functionmap = {
     'subst': SubstFunction,
     'patsubst': PatSubstFunction,
     'strip': StripFunction,
     'findstring': FindstringFunction,
--- a/pymake/parser.py
+++ b/pymake/parser.py
@@ -391,17 +391,17 @@ def iterlines(fd):
     for line in fd:
         lineno += 1
 
         if line.endswith('\r\n'):
             line = line[:-2] + '\n'
 
         yield (lineno, line)
 
-def setvariable(resolvevariables, setvariables, vname, token, d, offset,
+def setvariable(resolvevariables, setvariables, makefile, vname, token, d, offset,
                 iterfunc=itermakefilechars, source=data.Variables.SOURCE_MAKEFILE,
                 skipwhitespace=True):
     """
     Parse what's left in a data iterator di into a variable.
     """
     assert isinstance(resolvevariables, data.Variables)
     assert isinstance(setvariables, data.Variables)
 
@@ -409,17 +409,17 @@ def setvariable(resolvevariables, setvar
 
     if len(vname) == 0:
         raise SyntaxError("Empty variable name", loc=d.getloc(offset))
 
     if token == '+=':
         val = ''.join((c for c, t, o, oo in iterfunc(d, offset, emptytokenlist)))
         if skipwhitespace:
             val = val.lstrip()
-        setvariables.append(vname, source, val, resolvevariables)
+        setvariables.append(vname, source, val, resolvevariables, makefile)
         return
 
     if token == '?=':
         flavor = data.Variables.FLAVOR_RECURSIVE
         val = ''.join((c for c, t, o, oo in iterfunc(d, offset, emptytokenlist)))
         if skipwhitespace:
             val = val.lstrip()
         oldflavor, oldsource, oldval = setvariables.get(vname, expand=False)
@@ -432,17 +432,17 @@ def setvariable(resolvevariables, setvar
             val = val.lstrip()
     else:
         assert token == ':='
 
         flavor = data.Variables.FLAVOR_SIMPLE
         e, t, o = parsemakesyntax(d, offset, (), itermakefilechars)
         if skipwhitespace:
             e.lstrip()
-        val = e.resolve(resolvevariables)
+        val = e.resolve(makefile, resolvevariables)
         
     setvariables.set(vname, flavor, source, val)
 
 def parsecommandlineargs(makefile, args):
     """
     Given a set of arguments from a command-line invocation of make,
     parse out the variable definitions and return the rest as targets.
     """
@@ -456,17 +456,17 @@ def parsecommandlineargs(makefile, args)
             vname, t, val = a.partition('=')
         if t != '':
             makefile.overrides.append(a)
 
             vname = vname.strip()
             d = Data(None, None)
             d.append(val, Location('<command-line>', i, len(vname) + len(t)))
 
-            setvariable(makefile.variables, makefile.variables,
+            setvariable(makefile.variables, makefile.variables, makefile,
                         vname, t, d, 0, source=data.Variables.SOURCE_COMMANDLINE,
                         iterfunc=iterdata)
         else:
             r.append(a)
 
     return r
 
 eqargstokenlist = TokenList.get(('(', "'", '"'))
@@ -502,29 +502,29 @@ def ifeq(d, offset, makefile):
         token = d[offset]
         if token not in '\'"':
             raise SyntaxError("Unexpected text in conditional", d.getloc(offset))
 
         arg2, t, offset = parsemakesyntax(d, offset + 1, (token,), itermakefilechars)
 
         ensureend(d, offset, "Unexpected text after conditional")
 
-    val1 = arg1.resolve(makefile.variables)
-    val2 = arg2.resolve(makefile.variables)
+    val1 = arg1.resolve(makefile, makefile.variables)
+    val2 = arg2.resolve(makefile, makefile.variables)
 
     return val1 == val2
 
 def ifneq(d, offset, makefile):
     return not ifeq(d, offset, makefile)
 
 def ifdef(d, offset, makefile):
     e, t, offset = parsemakesyntax(d, offset, (), itermakefilechars)
     e.rstrip()
 
-    vname = e.resolve(makefile.variables)
+    vname = e.resolve(makefile, makefile.variables)
 
     flavor, source, value = makefile.variables.get(vname, expand=False)
 
     if value is None:
         return False
 
     # We aren't expanding the variable... we're just seeing if it was set to a non-empty
     # expansion.
@@ -641,110 +641,110 @@ def parsestream(fd, filename, makefile):
                 raise SyntaxError("Unmatched endef", d.getloc(offset))
 
             if kword == 'define':
                 e, t, i = parsemakesyntax(d, offset, (), itermakefilechars)
 
                 d = Data(fdlines, filename)
                 d.readline()
 
-                setvariable(makefile.variables, makefile.variables,
-                            e.resolve(makefile.variables),
+                setvariable(makefile.variables, makefile.variables, makefile,
+                            e.resolve(makefile, makefile.variables),
                             '=', d, 0, iterdefinechars,
                             skipwhitespace=False)
 
                 continue
 
             if kword in ('include', '-include'):
                 incfile, t, offset = parsemakesyntax(d, offset, (), itermakefilechars)
-                files = data.splitwords(incfile.resolve(makefile.variables))
+                files = data.splitwords(incfile.resolve(makefile, makefile.variables))
                 for f in files:
                     makefile.include(f, kword == 'include', loc=d.getloc(offset))
                 continue
 
             if kword == 'override':
                 e, token, offset = parsemakesyntax(d, offset, varsettokens, itermakefilechars)
                 e.lstrip()
                 e.rstrip()
 
                 if token is None:
                     raise SyntaxError("Malformed override directive, need =", d.getloc(offset))
 
-                vname = e.resolve(makefile.variables)
-                setvariable(makefile.variables, makefile.variables,
+                vname = e.resolve(makefile, makefile.variables)
+                setvariable(makefile.variables, makefile.variables, makefile,
                             vname, token, d, offset,
                             source=data.Variables.SOURCE_OVERRIDE)
                 continue
 
             if kword == 'export':
                 e, token, offset = parsemakesyntax(d, offset, varsettokens, itermakefilechars)
                 e.lstrip()
                 e.rstrip()
-                vars = e.resolve(makefile.variables)
+                vars = e.resolve(makefile, makefile.variables)
                 if token is None:
                     vlist = data.splitwords(vars)
                     if len(vlist) == 0:
                         raise SyntaxError("Exporting all variables is not supported", d.getloc(offset))
                 else:
                     vlist = [vars]
-                    setvariable(makefile.variables, makefile.variables,
+                    setvariable(makefile.variables, makefile.variables, makefile,
                                 vars, token, d, offset)
 
                 for v in vlist:
                     makefile.exportedvars.add(v)
 
                 continue
 
             if kword == 'unexport':
                 raise SyntaxError("unexporting variables is not supported", d.getloc(offset))
 
             assert kword is None, "unexpected kword: %r" % (kword,)
 
             e, token, offset = parsemakesyntax(d, offset, varsettokens + ('::', ':'), itermakefilechars)
             if token is None:
-                v = e.resolve(makefile.variables)
+                v = e.resolve(makefile, makefile.variables)
                 if v.strip() != '':
                     raise SyntaxError("Bad syntax: non-empty line is not a variable assignment or rule.", loc=d.getloc(0))
                 continue
 
             # if we encountered real makefile syntax, the current rule is over
             currule = None
 
             if token in varsettokens:
                 e.lstrip()
                 e.rstrip()
-                vname = e.resolve(makefile.variables)
-                setvariable(makefile.variables, makefile.variables,
+                vname = e.resolve(makefile, makefile.variables)
+                setvariable(makefile.variables, makefile.variables, makefile,
                             vname, token, d, offset)
             else:
                 doublecolon = token == '::'
 
                 # `e` is targets or target patterns, which can end up as
                 # * a rule
                 # * an implicit rule
                 # * a static pattern rule
                 # * a target-specific variable definition
                 # * a pattern-specific variable definition
                 # any of the rules may have order-only prerequisites
                 # delimited by |, and a command delimited by ;
-                targets = map(data.Pattern, data.splitwords(e.resolve(makefile.variables)))
+                targets = map(data.Pattern, data.splitwords(e.resolve(makefile, makefile.variables)))
 
                 if len(targets):
                     ispatterns = set((t.ispattern() for t in targets))
                     if len(ispatterns) == 2:
                         raise SyntaxError("Mixed implicit and normal rule", d.getloc(offset))
                     ispattern, = ispatterns
                 else:
                     ispattern = False
 
                 e, token, offset = parsemakesyntax(d, offset,
                                                    varsettokens + (':', '|', ';'),
                                                    itermakefilechars)
                 if token in (None, ';'):
-                    prereqs = data.splitwords(e.resolve(makefile.variables))
+                    prereqs = data.splitwords(e.resolve(makefile, makefile.variables))
                     if ispattern:
                         currule = data.PatternRule(targets, map(data.Pattern, prereqs), doublecolon, loc=d.getloc(0))
                         makefile.appendimplicitrule(currule)
                     else:
                         currule = data.Rule(prereqs, doublecolon, loc=d.getloc(0))
                         for t in targets:
                             makefile.gettarget(t.gettarget()).addrule(currule)
                         if len(targets):
@@ -752,45 +752,45 @@ def parsestream(fd, filename, makefile):
 
                     if token == ';':
                         offset = d.skipwhitespace(offset)
                         e, t, offset = parsemakesyntax(d, offset, (), itercommandchars)
                         currule.addcommand(e)
                 elif token in varsettokens:
                     e.lstrip()
                     e.rstrip()
-                    vname = e.resolve(makefile.variables)
+                    vname = e.resolve(makefile, makefile.variables)
                     if ispattern:
                         for target in targets:
                             setvariable(makefile.variables,
-                                        makefile.getpatternvariables(target), vname,
+                                        makefile.getpatternvariables(target), makefile, vname,
                                         token, d, offset)
                     else:
                         for target in targets:
                             setvariable(makefile.variables,
-                                        makefile.gettarget(target.gettarget()).variables,
+                                        makefile.gettarget(target.gettarget()).variables, makefile,
                                         vname, token, d, offset)
                 elif token == '|':
                     raise NotImplementedError('order-only prerequisites not implemented')
                 else:
                     assert token == ':'
 
                     # static pattern rule
                     if ispattern:
                         raise SyntaxError("static pattern rules must have static targets", d.getloc(0))
 
-                    patstr = e.resolve(makefile.variables)
+                    patstr = e.resolve(makefile, makefile.variables)
                     patterns = data.splitwords(patstr)
                     if len(patterns) != 1:
                         raise SyntaxError("A static pattern rule may have only one pattern", d.getloc(offset))
 
                     pattern = data.Pattern(patterns[0])
 
                     e, token, offset = parsemakesyntax(d, offset, (';',), itermakefilechars)
-                    prereqs = map(data.Pattern, data.splitwords(e.resolve(makefile.variables)))
+                    prereqs = map(data.Pattern, data.splitwords(e.resolve(makefile, makefile.variables)))
                     currule = data.PatternRule([pattern], prereqs, doublecolon, loc=d.getloc(0))
 
                     for t in targets:
                         tname = t.gettarget()
                         stem = pattern.match(tname)
                         if stem is None:
                             raise SyntaxError("Target '%s' of static pattern rule does not match pattern '%s'" % (tname, pattern), d.getloc(0))
                         pinstance = data.PatternRuleInstance(currule, '', stem, pattern.ismatchany())