Investigate getting rid of LazyCmdGenerator
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Mon, 15 Nov 2004 01:48:36 +0000 (01:48 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Mon, 15 Nov 2004 01:48:36 +0000 (01:48 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@1161 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/engine/SCons/Action.py
src/engine/SCons/ActionTests.py
src/engine/SCons/EnvironmentTests.py

index 3b62702bd28304dfcb7afb66b5edec3b8134bcee..a8e6c5b2f3f877069159f42d90f90c4b5f91a035 100644 (file)
@@ -185,8 +185,7 @@ def _do_create_action(act, *args, **kw):
             # of that Environment variable, so a user could put something
             # like a function or a CommandGenerator in that variable
             # instead of a string.
-            lcg = LazyCmdGenerator(var)
-            return apply(CommandGeneratorAction, (lcg,)+args, kw)
+            return apply(LazyAction, (var,)+args, kw)
         commands = string.split(str(act), '\n')
         if len(commands) == 1:
             return apply(CommandAction, (commands[0],)+args, kw)
@@ -234,7 +233,7 @@ class ActionBase:
         # it may call LazyCmdGenerator, which looks up a key
         # in that env.  So we temporarily remember the env here,
         # and CommandGeneratorAction will use this env
-        # when it calls its __generate method.
+        # when it calls its _generate method.
         self.presub_env = env
         lines = string.split(str(self), '\n')
         self.presub_env = None      # don't need this any more
@@ -431,7 +430,7 @@ class CommandGeneratorAction(ActionBase):
         self.generator = generator
         self.gen_kw = kw
 
-    def __generate(self, target, source, env, for_signature):
+    def _generate(self, target, source, env, for_signature):
         # ensure that target is a list, to make it easier to write
         # generator functions:
         if not SCons.Util.is_List(target):
@@ -448,15 +447,15 @@ class CommandGeneratorAction(ActionBase):
             env = self.presub_env or {}
         except AttributeError:
             env = {}
-        act = self.__generate([], [], env, 1)
+        act = self._generate([], [], env, 1)
         return str(act)
 
     def genstring(self, target, source, env):
-        return self.__generate(target, source, env, 1).genstring(target, source, env)
+        return self._generate(target, source, env, 1).genstring(target, source, env)
 
     def __call__(self, target, source, env, errfunc=None, presub=_null,
                  show=_null, execute=_null, chdir=_null):
-        act = self.__generate(target, source, env, 0)
+        act = self._generate(target, source, env, 0)
         return act(target, source, env, errfunc, presub,
                    show, execute, chdir)
 
@@ -466,30 +465,55 @@ class CommandGeneratorAction(ActionBase):
         This strips $(-$) and everything in between the string,
         since those parts don't affect signatures.
         """
-        return self.__generate(target, source, env, 1).get_contents(target, source, env, dict=None)
-
-class LazyCmdGenerator:
-    """This is not really an Action, although it kind of looks like one.
-    This is really a simple callable class that acts as a command
-    generator.  It holds on to a key into an Environment dictionary,
-    then waits until execution time to see what type it is, then tries
-    to create an Action out of it."""
-    def __init__(self, var):
+        return self._generate(target, source, env, 1).get_contents(target, source, env, dict=None)
+
+# Ooh, polymorphism -- pretty scary, eh, kids?
+#
+# A LazyCmdAction is a kind of hybrid generator and command action for
+# strings of the form "$VAR".  These strings normally expand to other
+# strings (think "$CCCOM" to "$CC -c -o $TARGET $SOURCE"), but we also
+# want to be able to replace them with functions in the construction
+# environment.  Consequently, we want lazy evaluation and creation of
+# an Action in the case of the function, but that's overkill in the more
+# normal case of expansion to other strings.
+#
+# So we do this with a subclass that's both a generator *and*
+# a command action.  The overridden methods all do a quick check
+# of the construction variable, and if it's a string we just call
+# the corresponding CommandAction method to do the heavy lifting.
+# If not, then we call the same-named CommandGeneratorAction method.
+# The CommandGeneratorAction methods work by using the overridden
+# _generate() method, uses our own way of handling "generation" of an
+# action based on what's in the construction variable.
+
+class LazyAction(CommandGeneratorAction, CommandAction):
+    def __init__(self, var, *args, **kw):
         if __debug__: logInstanceCreation(self)
+        apply(CommandAction.__init__, (self, '$'+var)+args, kw)
         self.var = SCons.Util.to_String(var)
+        self.gen_kw = kw
 
-    def __str__(self):
-        return 'LazyCmdGenerator: %s'%str(self.var)
+    def get_parent_class(self, env):
+        c = env.get(self.var)
+        if SCons.Util.is_String(c) and not '\n' in c:
+            return CommandAction
+        return CommandGeneratorAction
 
-    def __call__(self, target, source, env, for_signature):
-        try:
-            return env[self.var]
-        except KeyError:
-            # The variable reference substitutes to nothing.
-            return ''
+    def _generate(self, target, source, env, for_signature):
+        c = env.get(self.var, '')
+        gen_cmd = apply(Action, (c,), self.gen_kw)
+        if not gen_cmd:
+            raise SCons.Errors.UserError("$%s value %s cannot be used to create an Action." % (self.var, repr(c)))
+        return gen_cmd
 
-    def __cmp__(self, other):
-        return cmp(self.__dict__, other)
+    def __call__(self, target, source, env, *args, **kw):
+        args = (self, target, source, env) + args
+        c = self.get_parent_class(env)
+        return apply(c.__call__, args, kw)
+
+    def get_contents(self, target, source, env, dict=None):
+        c = self.get_parent_class(env)
+        return c.get_contents(self, target, source, env, dict)
 
 class FunctionAction(_ActionAction):
     """Class for Python function actions."""
index 74812df45626497fbebba52d7259d6837fd99946..f52496a25e4ef59fc2b5e2db4ebd3a523a7b30af 100644 (file)
@@ -298,12 +298,10 @@ class ActionTestCase(unittest.TestCase):
             pass
 
         a1 = SCons.Action.Action("$FOO")
-        assert isinstance(a1, SCons.Action.CommandGeneratorAction), a1
-        assert isinstance(a1.generator, SCons.Action.LazyCmdGenerator), a1.generator
+        assert isinstance(a1, SCons.Action.LazyAction), a1
 
         a2 = SCons.Action.Action("$FOO", strfunction=foo)
-        assert isinstance(a2, SCons.Action.CommandGeneratorAction), a2
-        assert isinstance(a2.generator, SCons.Action.LazyCmdGenerator), a2.generator
+        assert isinstance(a2, SCons.Action.LazyAction), a2
 
     def test_no_action(self):
         """Test when the Action() factory can't create an action object
@@ -1468,16 +1466,16 @@ class LazyActionTestCase(unittest.TestCase):
     def test___init__(self):
         """Test creation of a lazy-evaluation Action
         """
-        # Environment variable references should create a special
-        # type of CommandGeneratorAction that lazily evaluates the
-        # variable.
+        # Environment variable references should create a special type
+        # of LazyAction that lazily evaluates the variable for whether
+        # it's a string or something else before doing anything.
         a9 = SCons.Action.Action('$FOO')
-        assert isinstance(a9, SCons.Action.CommandGeneratorAction), a9
-        assert a9.generator.var == 'FOO', a9.generator.var
+        assert isinstance(a9, SCons.Action.LazyAction), a9
+        assert a9.var == 'FOO', a9.var
 
         a10 = SCons.Action.Action('${FOO}')
-        assert isinstance(a9, SCons.Action.CommandGeneratorAction), a10
-        assert a10.generator.var == 'FOO', a10.generator.var
+        assert isinstance(a10, SCons.Action.LazyAction), a10
+        assert a10.var == 'FOO', a10.var
 
     def test_genstring(self):
         """Test the lazy-evaluation Action genstring() method
@@ -1487,6 +1485,8 @@ class LazyActionTestCase(unittest.TestCase):
         a = SCons.Action.Action('$BAR')
         s = a.genstring([], [], env=Environment(BAR=f, s=self))
         assert s == "f(target, source, env)", s
+        s = a.genstring([], [], env=Environment(BAR='xxx', s=self))
+        assert s == 'xxx', s
 
     def test_execute(self):
         """Test executing a lazy-evaluation Action
@@ -1498,6 +1498,10 @@ class LazyActionTestCase(unittest.TestCase):
         a = SCons.Action.Action('$BAR')
         a([], [], env=Environment(BAR = f, s = self))
         assert self.test == 1, self.test
+        cmd = r'%s %s %s lazy' % (python, act_py, outfile)
+        a([], [], env=Environment(BAR = cmd, s = self))
+        c = test.read(outfile, 'r')
+        assert c == "act.py: 'lazy'\n", c
 
     def test_get_contents(self):
         """Test fetching the contents of a lazy-evaluation Action
index 4c9a0be155c59d494ca8111aadf808711937c586..71383e7a7707964cbffcf767562bc2acacc5d16e 100644 (file)
@@ -1756,7 +1756,7 @@ f5: \
 
         a = env.Action('$FOO')
         assert a, a
-        assert a.__class__ is SCons.Action.CommandGeneratorAction, a
+        assert a.__class__ is SCons.Action.LazyAction, a
 
         a = env.Action(['$FOO', 'foo'])
         assert a, a