Implement $(eval) during first-phase parsing.
authorBenjamin Smedberg <>
Tue, 17 Feb 2009 10:46:27 -0500
changeset 121 ace16e634043
parent 120 0d43efb31b37
child 122 1995f94b1c2f
push id67
push dateTue, 17 Feb 2009 15:46:32 +0000
Implement $(eval) during first-phase parsing.
--- a/README
+++ b/README
@@ -16,29 +16,32 @@ The Mozilla project inspired this tool w
 * Enable experiments with build system. By writing a makefile parser, we can experiment
   with converting in-tree makefiles to another build system, such as SCons, waf, ant, ...insert
   your favorite build tool here. Or we could experiment along the lines of makepp, keeping
   our existing makefiles, but change the engine to build a global dependency graph.
-* $(eval) is not yet supported
 * Parallel builds (-j > 1) are not yet supported
 * The vpath directive is not yet supported
 * Order-only prerequisites are not yet supported
+* Secondary expansion is not yet supported.
 * Target-specific variables behave differently than in GNU make: in pymake, the target-specific
   variable only applies to the specific target that is mentioned, and does not apply recursively
   to all dependencies which are remade. This is an intentional change: the behavior of GNU make
   is neither deterministic nor intuitive.
+* $(eval) is only supported during the parse phase. Any attempt to recursively expand
+  an $(eval) function during command execution will fail. This is an intentional incompatibility.
 * Windows is unlikely to work properly. There are subtle issues to figure out with Windows
   file paths and shell execution, because Python is not an MSYS project but we'd like it to use
   the MSYS shell when appropriate/necessary.
 * There is a subtle difference in execution order that can cause unexpected changes in the
   following circumstance:
 ** A file `foo.c` exists on the VPATH
 ** A rule for `foo.c` exists with a dependency on `tool` and no commands
--- a/pymake/
+++ b/pymake/
@@ -1,14 +1,16 @@
 Makefile functions.
+import pymake
 from pymake import data
 import subprocess, os, glob
+from cStringIO import StringIO
 log = data.log
 class Function(object):
     An object that represents a function call. This class is always subclassed
     with the following methods and attributes:
@@ -492,17 +494,24 @@ class ValueFunction(Function):
         return value
 class EvalFunction(Function):
     name = 'eval'
     minargs = 1
     maxargs = 1
     def resolve(self, makefile, variables, setting):
-        raise NotImplementedError('no eval yet')
+        if makefile.parsingfinished:
+            # GNU make allows variables to be set by recursive expansion during
+            # command execution. This seems really dumb to me, so I don't!
+            raise data.DataError("$(eval) not allowed via recursive expansion after parsing is finished", self.loc)
+        text = StringIO(self._arguments[0].resolve(makefile, variables, setting))
+        pymake.parser.parsestream(text, 'evaluation from %s' % self.loc, makefile)
+        return ''
 class OriginFunction(Function):
     name = 'origin'
     minargs = 1
     maxargs = 1
     def resolve(self, makefile, variables, setting):
         vname = self._arguments[0].resolve(makefile, variables, setting)
new file mode 100644
--- /dev/null
+++ b/tests/
@@ -0,0 +1,12 @@
+#T returncode: 2
+# Once parsing is finished, recursive expansion in commands are not allowed to create any new rules (it may only set variables)
+define MORERULE
+	@echo TEST-FAIL
+	$(eval $(MORERULE))
+	@echo done
new file mode 100644
--- /dev/null
+++ b/tests/
@@ -0,0 +1,7 @@
+TESTVAR = val1
+$(eval TESTVAR = val2)
+	test "$(TESTVAR)" = "val2"
+	@echo TEST-PASS