Group --debug=count output by object modules.
[scons.git] / src / engine / SCons / Action.py
index 63d8c9870d419ba66e440f9cebf316e8dcfa135a..077ddfb60d959f603bee66ca160b9bbd2103fa72 100644 (file)
@@ -8,6 +8,11 @@ The base class here is ActionBase.  The base class supplies just a few
 OO utility methods and some generic methods for displaying information
 about an Action in response to the various commands that control printing.
 
+A second-level base class is _ActionAction.  This extends ActionBase
+by providing the methods that can be used to show and perform an
+action.  True Action objects will subclass _ActionAction; Action
+factory class objects will subclass ActionBase.
+
 The heavy lifting is handled by subclasses for the different types of
 actions we might execute:
 
@@ -30,27 +35,21 @@ other modules:
         needs to be rebuilt because its action changed.
 
     genstring()
-        Returns a string representation of the Action *without* command
-        substitution, but allows a CommandGeneratorAction to generate
-        the right action based on the specified target, source and env.
-        This is used by the Signature subsystem (through the Executor)
-        to compare the actions used to build a target last time and
-        this time.
+        Returns a string representation of the Action *without*
+        command substitution, but allows a CommandGeneratorAction to
+        generate the right action based on the specified target,
+        source and env.  This is used by the Signature subsystem
+        (through the Executor) to obtain an (imprecise) representation
+        of the Action operation for informative purposes.
+
 
 Subclasses also supply the following methods for internal use within
 this module:
 
     __str__()
-        Returns a string representation of the Action *without* command
-        substitution.  This is used by the __call__() methods to display
-        the pre-substitution command whenever the --debug=presub option
-        is used.
-
-    strfunction()
-        Returns a substituted string representation of the Action.
-        This is used by the ActionBase.show() command to display the
-        command/function that will be executed to generate the target(s).
-
+        Returns a string approximation of the Action; no variable
+        substitution is performed.
+        
     execute()
         The internal method that really, truly, actually handles the
         execution of a command or Python function.  This is used so
@@ -58,6 +57,11 @@ this module:
         pre-substitution representations, and *then* execute an action
         without worrying about the specific Actions involved.
 
+    strfunction()
+        Returns a substituted string representation of the Action.
+        This is used by the _ActionAction.show() command to display the
+        command/function that will be executed to generate the target(s).
+
 There is a related independent ActionCaller class that looks like a
 regular Action, and which serves as a wrapper for arbitrary functions
 that we want to let the user specify the arguments to now, but actually
@@ -181,42 +185,80 @@ 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)
         else:
-            listCmdActions = map(lambda x: CommandAction(x), commands)
-            return apply(ListAction, (listCmdActions,)+args, kw)
+            listCmdActions = map(lambda x, args=args, kw=kw:
+                                 apply(CommandAction, (x,)+args, kw),
+                                 commands)
+            return ListAction(listCmdActions)
     return None
 
-def Action(act, strfunction=_null, varlist=[], presub=_null):
+def Action(act, *args, **kw):
     """A factory for action objects."""
     if SCons.Util.is_List(act):
-        acts = map(lambda x, s=strfunction, v=varlist, ps=presub:
-                          _do_create_action(x, strfunction=s, varlist=v, presub=ps),
+        acts = map(lambda a, args=args, kw=kw:
+                          apply(_do_create_action, (a,)+args, kw),
                    act)
-        acts = filter(lambda x: not x is None, acts)
+        acts = filter(None, acts)
         if len(acts) == 1:
             return acts[0]
         else:
-            return ListAction(acts, strfunction=strfunction, varlist=varlist, presub=presub)
+            return ListAction(acts)
     else:
-        return _do_create_action(act, strfunction=strfunction, varlist=varlist, presub=presub)
+        return apply(_do_create_action, (act,)+args, kw)
 
 class ActionBase:
+    """Base class for all types of action objects that can be held by
+    other objects (Builders, Executors, etc.)  This provides the
+    common methods for manipulating and combining those actions."""
+    
+    __metaclass__ = SCons.Memoize.Memoized_Metaclass
+
+    def __cmp__(self, other):
+        return cmp(self.__dict__, other)
+
+    def genstring(self, target, source, env):
+        return str(self)
+
+    def __add__(self, other):
+        return _actionAppend(self, other)
+
+    def __radd__(self, other):
+        return _actionAppend(other, self)
+
+    def presub_lines(self, env):
+        # CommandGeneratorAction needs a real environment
+        # in order to return the proper string here, since
+        # it may call LazyAction, 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.
+        self.presub_env = env
+        lines = string.split(str(self), '\n')
+        self.presub_env = None      # don't need this any more
+        return lines
+
+if not SCons.Memoize.has_metaclass:
+    _Base = ActionBase
+    class ActionBase(SCons.Memoize.Memoizer, _Base):
+        "Cache-backed version of ActionBase"
+        def __init__(self, *args, **kw):
+            apply(_Base.__init__, (self,)+args, kw)
+            SCons.Memoize.Memoizer.__init__(self)
+
+
+class _ActionAction(ActionBase):
     """Base class for actions that create output objects."""
-    def __init__(self, strfunction=_null, presub=_null, **kw):
+    def __init__(self, strfunction=_null, presub=_null, chdir=None, **kw):
         if not strfunction is _null:
             self.strfunction = strfunction
         if presub is _null:
-            self.presub = print_actions_presub
-        else:
-            self.presub = presub
-
-    def __cmp__(self, other):
-        return cmp(self.__dict__, other)
+            presub = print_actions_presub
+        self.presub = presub
+        self.chdir = chdir
 
     def print_cmd_line(self, s, target, source, env):
         sys.stdout.write(s + "\n")
@@ -225,7 +267,8 @@ class ActionBase:
                                errfunc=None,
                                presub=_null,
                                show=_null,
-                               execute=_null):
+                               execute=_null,
+                               chdir=_null):
         if not SCons.Util.is_List(target):
             target = [target]
         if not SCons.Util.is_List(source):
@@ -233,14 +276,26 @@ class ActionBase:
         if presub is _null:  presub = self.presub
         if show is _null:  show = print_actions
         if execute is _null:  execute = execute_actions
+        if chdir is _null: chdir = self.chdir
+        save_cwd = None
+        if chdir:
+            save_cwd = os.getcwd()
+            try:
+                chdir = str(chdir.abspath)
+            except AttributeError:
+                if not SCons.Util.is_String(chdir):
+                    chdir = str(target[0].dir)
         if presub:
-            t = string.join(map(str, target), 'and')
+            t = string.join(map(str, target), ' and ')
             l = string.join(self.presub_lines(env), '\n  ')
-            out = "Building %s with action(s):\n  %s\n" % (t, l)
+            out = "Building %s with action:\n  %s\n" % (t, l)
             sys.stdout.write(out)
+        s = None
         if show and self.strfunction:
             s = self.strfunction(target, source, env)
             if s:
+                if chdir:
+                    s = ('os.chdir(%s)\n' % repr(chdir)) + s
                 try:
                     get = env.get
                 except AttributeError:
@@ -250,37 +305,21 @@ class ActionBase:
                     if not print_func:
                         print_func = self.print_cmd_line
                 print_func(s, target, source, env)
+        stat = 0
         if execute:
-            stat = self.execute(target, source, env)
-            if stat and errfunc:
-                errfunc(stat)
-            return stat
-        else:
-            return 0
-
-    def presub_lines(self, env):
-        # CommandGeneratorAction needs a real environment
-        # in order to return the proper string here, since
-        # 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.
-        self.presub_env = env
-        lines = string.split(str(self), '\n')
-        self.presub_env = None      # don't need this any more
-        return lines
-
-    def genstring(self, target, source, env):
-        return str(self)
-
-    def get_actions(self):
-        return [self]
-
-    def __add__(self, other):
-        return _actionAppend(self, other)
+            if chdir:
+                os.chdir(chdir)
+            try:
+                stat = self.execute(target, source, env)
+                if stat and errfunc:
+                    errfunc(stat)
+            finally:
+                if save_cwd:
+                    os.chdir(save_cwd)
+        if s and save_cwd:
+            print_func('os.chdir(%s)' % repr(save_cwd), target, source, env)
+        return stat
 
-    def __radd__(self, other):
-        return _actionAppend(other, self)
 
 def _string_from_cmd_list(cmd_list):
     """Takes a list of command line arguments and returns a pretty
@@ -292,22 +331,39 @@ def _string_from_cmd_list(cmd_list):
         cl.append(arg)
     return string.join(cl)
 
-class CommandAction(ActionBase):
+class CommandAction(_ActionAction):
     """Class for command-execution actions."""
-    def __init__(self, cmd, **kw):
-        # Cmd list can actually be a list or a single item...basically
-        # anything that we could pass in as the first arg to
-        # Environment.subst_list().
-        if __debug__: logInstanceCreation(self)
-        apply(ActionBase.__init__, (self,), kw)
+    def __init__(self, cmd, cmdstr=None, *args, **kw):
+        # Cmd can actually be a list or a single item; if it's a
+        # single item it should be the command string to execute; if a
+        # list then it should be the words of the command string to
+        # execute.  Only a single command should be executed by this
+        # object; lists of commands should be handled by embedding
+        # these objects in a ListAction object (which the Action()
+        # factory above does).  cmd will be passed to
+        # Environment.subst_list() for substituting environment
+        # variables.
+        if __debug__: logInstanceCreation(self, 'Action.CommandAction')
+        apply(_ActionAction.__init__, (self,)+args, kw)
+        if SCons.Util.is_List(cmd):
+            if filter(SCons.Util.is_List, cmd):
+                raise TypeError, "CommandAction should be given only " \
+                      "a single command"
         self.cmd_list = cmd
+        self.cmdstr = cmdstr
 
     def __str__(self):
+        if SCons.Util.is_List(self.cmd_list):
+            return string.join(map(str, self.cmd_list), ' ')
         return str(self.cmd_list)
 
     def strfunction(self, target, source, env):
+        if not self.cmdstr is None:
+            c = env.subst(self.cmdstr, 0, target, source)
+            if c:
+                return c
         cmd_list = env.subst_list(self.cmd_list, 0, target, source)
-        return string.join(map(_string_from_cmd_list, cmd_list), "\n")
+        return _string_from_cmd_list(cmd_list[0])
 
     def execute(self, target, source, env):
         """Execute a command action.
@@ -357,7 +413,8 @@ class CommandAction(ActionBase):
                     # reasonable for just about everything else:
                     ENV[key] = str(value)
 
-        cmd_list = env.subst_list(self.cmd_list, 0, target, source)
+        cmd_list = env.subst_list(self.cmd_list, 0, target,
+                                  map(rfile, source))
 
         # Use len() to filter out any "command" that's zero-length.
         for cmd_line in filter(len, cmd_list):
@@ -368,7 +425,7 @@ class CommandAction(ActionBase):
                 return result
         return 0
 
-    def get_contents(self, target, source, env, dict=None):
+    def get_contents(self, target, source, env):
         """Return the signature contents of this action's command line.
 
         This strips $(-$) and everything in between the string,
@@ -379,98 +436,123 @@ class CommandAction(ActionBase):
             cmd = string.join(map(str, cmd))
         else:
             cmd = str(cmd)
-        return env.subst_target_source(cmd, SCons.Util.SUBST_SIG, target, source, dict)
+        return env.subst_target_source(cmd, SCons.Util.SUBST_SIG, target, source)
 
 class CommandGeneratorAction(ActionBase):
     """Class for command-generator actions."""
-    def __init__(self, generator, **kw):
-        if __debug__: logInstanceCreation(self)
-        apply(ActionBase.__init__, (self,), kw)
+    def __init__(self, generator, *args, **kw):
+        if __debug__: logInstanceCreation(self, 'Action.CommandGeneratorAction')
         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):
             target = [target]
 
         ret = self.generator(target=target, source=source, env=env, for_signature=for_signature)
-        gen_cmd = Action(ret)
+        gen_cmd = apply(Action, (ret,), self.gen_kw)
         if not gen_cmd:
             raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
         return gen_cmd
 
-    def strfunction(self, target, source, env):
-        if not SCons.Util.is_List(source):
-            source = [source]
-        rsources = map(rfile, source)
-        act = self.__generate(target, source, env, 0)
-        if act.strfunction:
-            return act.strfunction(target, rsources, env)
-        else:
-            return None
-
     def __str__(self):
         try:
             env = self.presub_env or {}
         except AttributeError:
             env = {}
-        act = self.__generate([], [], env, 0)
+        act = self._generate([], [], env, 1)
         return str(act)
 
     def genstring(self, target, source, env):
-        return str(self.__generate(target, source, env, 0))
+        return self._generate(target, source, env, 1).genstring(target, source, env)
 
-    def execute(self, target, source, env):
-        rsources = map(rfile, source)
-        act = self.__generate(target, source, env, 0)
-        return act.execute(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)
+        return act(target, source, env, errfunc, presub,
+                   show, execute, chdir)
 
-    def get_contents(self, target, source, env, dict=None):
+    def get_contents(self, target, source, env):
         """Return the signature contents of this action's command line.
 
         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):
-        if __debug__: logInstanceCreation(self)
+        return self._generate(target, source, env, 1).get_contents(target, source, env)
+
+
+
+# A LazyAction 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, that is, our own way of handling "generation" of
+# an action based on what's in the construction variable.
+
+class LazyAction(CommandGeneratorAction, CommandAction):
+
+    __metaclass__ = SCons.Memoize.Memoized_Metaclass
+
+    def __init__(self, var, *args, **kw):
+        if __debug__: logInstanceCreation(self, 'Action.LazyAction')
+        apply(CommandAction.__init__, (self, '$'+var)+args, kw)
         self.var = SCons.Util.to_String(var)
+        self.gen_kw = kw
+
+    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 _generate_cache(self, env):
+        """__cacheable__"""
+        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 strfunction(self, target, source, env):
-        try:
-            return env[self.var]
-        except KeyError:
-            # The variable reference substitutes to nothing.
-            return ''
+    def _generate(self, target, source, env, for_signature):
+        return self._generate_cache(env)
 
-    def __str__(self):
-        return 'LazyCmdGenerator: %s'%str(self.var)
+    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 __call__(self, target, source, env, for_signature):
-        try:
-            return env[self.var]
-        except KeyError:
-            # The variable reference substitutes to nothing.
-            return ''
+    def get_contents(self, target, source, env):
+        c = self.get_parent_class(env)
+        return c.get_contents(self, target, source, env)
 
-    def __cmp__(self, other):
-        return cmp(self.__dict__, other)
+if not SCons.Memoize.has_metaclass:
+    _Base = LazyAction
+    class LazyAction(SCons.Memoize.Memoizer, _Base):
+        def __init__(self, *args, **kw):
+            SCons.Memoize.Memoizer.__init__(self)
+            apply(_Base.__init__, (self,)+args, kw)
 
-class FunctionAction(ActionBase):
+
+
+class FunctionAction(_ActionAction):
     """Class for Python function actions."""
 
-    def __init__(self, execfunction, **kw):
-        if __debug__: logInstanceCreation(self)
+    def __init__(self, execfunction, *args, **kw):
+        if __debug__: logInstanceCreation(self, 'Action.FunctionAction')
         self.execfunction = execfunction
-        apply(ActionBase.__init__, (self,), kw)
+        apply(_ActionAction.__init__, (self,)+args, kw)
         self.varlist = kw.get('varlist', [])
 
     def function_name(self):
@@ -487,19 +569,36 @@ class FunctionAction(ActionBase):
             def quote(s):
                 return '"' + str(s) + '"'
             return '[' + string.join(map(quote, a), ", ") + ']'
+        try:
+            strfunc = self.execfunction.strfunction
+        except AttributeError:
+            pass
+        else:
+            if strfunc is None:
+                return None
+            if callable(strfunc):
+                return strfunc(target, source, env)
         name = self.function_name()
         tstr = array(target)
         sstr = array(source)
         return "%s(%s, %s)" % (name, tstr, sstr)
 
     def __str__(self):
-        return "%s(env, target, source)" % self.function_name()
+        name = self.function_name()
+        if name == 'ActionCaller':
+            return str(self.execfunction)
+        return "%s(target, source, env)" % name
 
     def execute(self, target, source, env):
         rsources = map(rfile, source)
-        return self.execfunction(target=target, source=rsources, env=env)
+        try:
+            result = self.execfunction(target=target, source=rsources, env=env)
+        except EnvironmentError, e:
+            # If an IOError/OSError happens, raise a BuildError.
+            raise SCons.Errors.BuildError(node=target, errstr=e.strerror)
+        return result
 
-    def get_contents(self, target, source, env, dict=None):
+    def get_contents(self, target, source, env):
         """Return the signature contents of this callable action.
 
         By providing direct access to the code object of the
@@ -520,54 +619,53 @@ class FunctionAction(ActionBase):
                     # This is weird, just do the best we can.
                     contents = str(self.execfunction)
                 else:
-                    contents = gc(target, source, env, dict)
+                    contents = gc(target, source, env)
         return contents + env.subst(string.join(map(lambda v: '${'+v+'}',
                                                      self.varlist)))
 
 class ListAction(ActionBase):
     """Class for lists of other actions."""
-    def __init__(self, list, **kw):
-        if __debug__: logInstanceCreation(self)
-        apply(ActionBase.__init__, (self,), kw)
-        self.list = map(lambda x: Action(x), list)
+    def __init__(self, list):
+        if __debug__: logInstanceCreation(self, 'Action.ListAction')
+        def list_of_actions(x):
+            if isinstance(x, ActionBase):
+                return x
+            return Action(x)
+        self.list = map(list_of_actions, list)
 
-    def get_actions(self):
-        return self.list
+    def genstring(self, target, source, env):
+        return string.join(map(lambda a, t=target, s=source, e=env:
+                                  a.genstring(t, s, e),
+                               self.list),
+                           '\n')
 
     def __str__(self):
-        s = []
-        for l in self.list:
-            s.append(str(l))
-        return string.join(s, "\n")
-
-    def strfunction(self, target, source, env):
-        s = []
-        for l in self.list:
-            if l.strfunction:
-                x = l.strfunction(target, source, env)
-                if not SCons.Util.is_List(x):
-                    x = [x]
-                s.extend(x)
-        return string.join(s, "\n")
-
-    def execute(self, target, source, env):
-        for l in self.list:
-            r = l.execute(target, source, env)
-            if r:
-                return r
-        return 0
+        return string.join(map(str, self.list), '\n')
+    
+    def presub_lines(self, env):
+        return SCons.Util.flatten(map(lambda a, env=env:
+                                      a.presub_lines(env),
+                                      self.list))
 
-    def get_contents(self, target, source, env, dict=None):
+    def get_contents(self, target, source, env):
         """Return the signature contents of this action list.
 
         Simple concatenation of the signatures of the elements.
         """
-        dict = SCons.Util.subst_dict(target, source)
-        return string.join(map(lambda x, t=target, s=source, e=env, d=dict:
-                                      x.get_contents(t, s, e, d),
+        return string.join(map(lambda x, t=target, s=source, e=env:
+                                      x.get_contents(t, s, e),
                                self.list),
                            "")
 
+    def __call__(self, target, source, env, errfunc=None, presub=_null,
+                 show=_null, execute=_null, chdir=_null):
+        for act in self.list:
+            stat = act(target, source, env, errfunc, presub,
+                       show, execute, chdir)
+            if stat:
+                return stat
+        return 0
+
 class ActionCaller:
     """A class for delaying calling an Action function with specific
     (positional and keyword) arguments until the Action is actually
@@ -581,7 +679,7 @@ class ActionCaller:
         self.parent = parent
         self.args = args
         self.kw = kw
-    def get_contents(self, target, source, env, dict=None):
+    def get_contents(self, target, source, env):
         actfunc = self.parent.actfunc
         try:
             # "self.actfunc" is a function.
@@ -595,14 +693,22 @@ class ActionCaller:
                 # or something like that.  Do the best we can.
                 contents = str(actfunc)
         return contents
+    def subst(self, s, target, source, env):
+        # Special-case hack:  Let a custom function wrapped in an
+        # ActionCaller get at the environment through which the action
+        # was called by using this hard-coded value as a special return.
+        if s == '$__env__':
+            return env
+        else:
+            return env.subst(s, 0, target, source)
     def subst_args(self, target, source, env):
-        return map(lambda x, e=env, t=target, s=source:
-                          e.subst(x, 0, t, s),
+        return map(lambda x, self=self, t=target, s=source, e=env:
+                          self.subst(x, t, s, e),
                    self.args)
     def subst_kw(self, target, source, env):
         kw = {}
         for key in self.kw.keys():
-            kw[key] = env.subst(self.kw[key], 0, target, source)
+            kw[key] = self.subst(self.kw[key], target, source, env)
         return kw
     def __call__(self, target, source, env):
         args = self.subst_args(target, source, env)
@@ -612,6 +718,8 @@ class ActionCaller:
         args = self.subst_args(target, source, env)
         kw = self.subst_kw(target, source, env)
         return apply(self.parent.strfunc, args, kw)
+    def __str__(self):
+        return apply(self.parent.strfunc, self.args, self.kw)
 
 class ActionFactory:
     """A factory class that will wrap up an arbitrary function
@@ -627,4 +735,5 @@ class ActionFactory:
         self.strfunc = strfunc
     def __call__(self, *args, **kw):
         ac = ActionCaller(self, args, kw)
-        return Action(ac, strfunction=ac.strfunction)
+        action = Action(ac, strfunction=ac.strfunction)
+        return action