SCons.Action

Package SCons :: Module Action |
[hide private]
[frames] | no frames]

# Source Code for Module SCons.Action

       1  """SCons.Action
       2
       3  This encapsulates information about executing any sort of action that
       4  can build one or more target Nodes (typically files) from one or more
       5  source Nodes (also typically files) given a specific Environment.
       6
       7  The base class here is ActionBase.  The base class supplies just a few
       8  OO utility methods and some generic methods for displaying information
       9  about an Action in response to the various commands that control printing.
      10
      11  A second-level base class is _ActionAction.  This extends ActionBase
      12  by providing the methods that can be used to show and perform an
      13  action.  True Action objects will subclass _ActionAction; Action
      14  factory class objects will subclass ActionBase.
      15
      16  The heavy lifting is handled by subclasses for the different types of
      17  actions we might execute:
      18
      19      CommandAction
      20      CommandGeneratorAction
      21      FunctionAction
      22      ListAction
      23
      24  The subclasses supply the following public interface methods used by
      25  other modules:
      26
      27      __call__()
      28          THE public interface, "calling" an Action object executes the
      29          command or Python function.  This also takes care of printing
      30          a pre-substitution command for debugging purposes.
      31
      32      get_contents()
      33          Fetches the "contents" of an Action for signature calculation.
      34          This is what gets MD5 checksumm'ed to decide if a target needs
      35          to be rebuilt because its action changed.
      36
      37      genstring()
      38          Returns a string representation of the Action *without*
      39          command substitution, but allows a CommandGeneratorAction to
      40          generate the right action based on the specified target,
      41          source and env.  This is used by the Signature subsystem
      42          (through the Executor) to obtain an (imprecise) representation
      43          of the Action operation for informative purposes.
      44
      45
      46  Subclasses also supply the following methods for internal use within
      47  this module:
      48
      49      __str__()
      50          Returns a string approximation of the Action; no variable
      51          substitution is performed.
      52
      53      execute()
      54          The internal method that really, truly, actually handles the
      55          execution of a command or Python function.  This is used so
      56          that the __call__() methods can take care of displaying any
      57          pre-substitution representations, and *then* execute an action
      58          without worrying about the specific Actions involved.
      59
      60      strfunction()
      61          Returns a substituted string representation of the Action.
      62          This is used by the _ActionAction.show() command to display the
      63          command/function that will be executed to generate the target(s).
      64
      65  There is a related independent ActionCaller class that looks like a
      66  regular Action, and which serves as a wrapper for arbitrary functions
      67  that we want to let the user specify the arguments to now, but actually
      68  execute later (when an out-of-date check determines that it's needed to
      69  be executed, for example).  Objects of this class are returned by an
      70  ActionFactory class that provides a __call__() method as a convenient
      71  way for wrapping up the functions.
      72
      73  """
      74
      75  #
      76  # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation
      77  #
      78  # Permission is hereby granted, free of charge, to any person obtaining
      79  # a copy of this software and associated documentation files (the
      80  # "Software"), to deal in the Software without restriction, including
      81  # without limitation the rights to use, copy, modify, merge, publish,
      82  # distribute, sublicense, and/or sell copies of the Software, and to
      83  # permit persons to whom the Software is furnished to do so, subject to
      84  # the following conditions:
      85  #
      86  # The above copyright notice and this permission notice shall be included
      87  # in all copies or substantial portions of the Software.
      88  #
      89  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
      90  # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
      91  # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
      92  # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
      93  # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
      94  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
      95  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
      96  #
      97
      98  __revision__ = "src/engine/SCons/Action.py 2725 2008/03/31 12:52:02 knight"
      99
     100  import cPickle
     101  import dis
     102  import os
     103  import os.path
     104  import string
     105  import sys
     106
     107  from SCons.Debug import logInstanceCreation
     108  import SCons.Errors
     109  import SCons.Executor
     110  import SCons.Util
     111
     112 -class _Null:
     113      pass
     114
     115  _null = _Null
     116
     117  print_actions = 1
     118  execute_actions = 1
     119  print_actions_presub = 0
     120
     121  default_ENV = None
     122
     123 -def rfile(n):
     124      try:
     125          return n.rfile()
     126      except AttributeError:
     127          return n
     128
     129 -def default_exitstatfunc(s):
     130      return s
     131
     132  try:
     133      SET_LINENO = dis.SET_LINENO
     134      HAVE_ARGUMENT = dis.HAVE_ARGUMENT
     135  except AttributeError:
     136      remove_set_lineno_codes = lambda x: x
     137  else:
     138 -    def remove_set_lineno_codes(code):
     139          result = []
     140          n = len(code)
     141          i = 0
     142          while i < n:
     143              c = code[i]
     144              op = ord(c)
     145              if op >= HAVE_ARGUMENT:
     146                  if op != SET_LINENO:
     147                      result.append(code[i:i+3])
     148                  i = i+3
     149              else:
     150                  result.append(c)
     151                  i = i+1
     152          return string.join(result, '')
     153
     154
     155 -def _callable_contents(obj):
     156      """Return the signature contents of a callable Python object.
     157      """
     158      try:
     159          # Test if obj is a method.
     160          return _function_contents(obj.im_func)
     161
     162      except AttributeError:
     163          try:
     164              # Test if obj is a callable object.
     165              return _function_contents(obj.__call__.im_func)
     166
     167          except AttributeError:
     168              try:
     169                  # Test if obj is a code object.
     170                  return _code_contents(obj)
     171
     172              except AttributeError:
     173                      # Test if obj is a function object.
     174                      return _function_contents(obj)
     175
     176
     177 -def _object_contents(obj):
     178      """Return the signature contents of any Python object.
     179
     180      We have to handle the case where object contains a code object
     181      since it can be pickled directly.
     182      """
     183      try:
     184          # Test if obj is a method.
     185          return _function_contents(obj.im_func)
     186
     187      except AttributeError:
     188          try:
     189              # Test if obj is a callable object.
     190              return _function_contents(obj.__call__.im_func)
     191
     192          except AttributeError:
     193              try:
     194                  # Test if obj is a code object.
     195                  return _code_contents(obj)
     196
     197              except AttributeError:
     198                  try:
     199                      # Test if obj is a function object.
     200                      return _function_contents(obj)
     201
     202                  except AttributeError:
     203                      # Should be a pickable Python object.
     204                      try:
     205                          return cPickle.dumps(obj)
     206                      except (cPickle.PicklingError, TypeError):
     207                          # This is weird, but it seems that nested classes
     208                          # are unpickable. The Python docs say it should
     209                          # always be a PicklingError, but some Python
     210                          # versions seem to return TypeError.  Just do
     211                          # the best we can.
     212                          return str(obj)
     213
     214
     215 -def _code_contents(code):
     216      """Return the signature contents of a code object.
     217
     218      By providing direct access to the code object of the
     219      function, Python makes this extremely easy.  Hooray!
     220
     221      Unfortunately, older versions of Python include line
     222      number indications in the compiled byte code.  Boo!
     223      So we remove the line number byte codes to prevent
     224      recompilations from moving a Python function.
     225      """
     226
     227      contents = []
     228
     229      # The code contents depends on the number of local variables
     230      # but not their actual names.
     231      contents.append("%s,%s" % (code.co_argcount, len(code.co_varnames)))
     232      try:
     233          contents.append(",%s,%s" % (len(code.co_cellvars), len(code.co_freevars)))
     234      except AttributeError:
     235          # Older versions of Python do not support closures.
     236          contents.append(",0,0")
     237
     238      # The code contents depends on any constants accessed by the
     239      # function. Note that we have to call _object_contents on each
     240      # constants because the code object of nested functions can
     241      # show-up among the constants.
     242      #
     243      # Note that we also always ignore the first entry of co_consts
     244      # which contains the function doc string. We assume that the
     245      # function does not access its doc string.
     246      contents.append(',(' + string.join(map(_object_contents,code.co_consts[1:]),',') + ')')
     247
     248      # The code contents depends on the variable names used to
     249      # accessed global variable, as changing the variable name changes
     250      # the variable actually accessed and therefore changes the
     251      # function result.
     252      contents.append(',(' + string.join(map(_object_contents,code.co_names),',') + ')')
     253
     254
     255      # The code contents depends on its actual code!!!
     256      contents.append(',(' + str(remove_set_lineno_codes(code.co_code)) + ')')
     257
     258      return string.join(contents, '')
     259
     260
     261 -def _function_contents(func):
     262      """Return the signature contents of a function."""
     263
     264      contents = [_code_contents(func.func_code)]
     265
     266      # The function contents depends on the value of defaults arguments
     267      if func.func_defaults:
     268          contents.append(',(' + string.join(map(_object_contents,func.func_defaults),',') + ')')
     269      else:
     270          contents.append(',()')
     271
     272      # The function contents depends on the closure captured cell values.
     273      try:
     274          closure = func.func_closure or []
     275      except AttributeError:
     276          # Older versions of Python do not support closures.
     277          closure = []
     278
     279      #xxx = [_object_contents(x.cell_contents) for x in closure]
     280      xxx = map(lambda x: _object_contents(x.cell_contents), closure)
     281      contents.append(',(' + string.join(xxx, ',') + ')')
     282
     283      return string.join(contents, '')
     284
     285
     286 -def _actionAppend(act1, act2):
     287      # This function knows how to slap two actions together.
     288      # Mainly, it handles ListActions by concatenating into
     289      # a single ListAction.
     290      a1 = Action(act1)
     291      a2 = Action(act2)
     292      if a1 is None or a2 is None:
     293          raise TypeError, "Cannot append %s to %s" % (type(act1), type(act2))
     294      if isinstance(a1, ListAction):
     295          if isinstance(a2, ListAction):
     296              return ListAction(a1.list + a2.list)
     297          else:
     298              return ListAction(a1.list + [ a2 ])
     299      else:
     300          if isinstance(a2, ListAction):
     301              return ListAction([ a1 ] + a2.list)
     302          else:
     303              return ListAction([ a1, a2 ])
     304
     305 -def _do_create_action(act, *args, **kw):
     306      """This is the actual "implementation" for the
     307      Action factory method, below.  This handles the
     308      fact that passing lists to Action() itself has
     309      different semantics than passing lists as elements
     310      of lists.
     311
     312      The former will create a ListAction, the latter
     313      will create a CommandAction by converting the inner
     314      list elements to strings."""
     315
     316      if isinstance(act, ActionBase):
     317          return act
     318      if SCons.Util.is_List(act):
     319          return apply(CommandAction, (act,)+args, kw)
     320      if callable(act):
     321          try:
     322              gen = kw['generator']
     323              del kw['generator']
     324          except KeyError:
     325              gen = 0
     326          if gen:
     327              action_type = CommandGeneratorAction
     328          else:
     329              action_type = FunctionAction
     330          return apply(action_type, (act,)+args, kw)
     331      if SCons.Util.is_String(act):
     332          var=SCons.Util.get_environment_var(act)
     333          if var:
     334              # This looks like a string that is purely an Environment
     335              # variable reference, like "$FOO" or "${FOO}".  We do
     336              # something special here...we lazily evaluate the contents
     337              # of that Environment variable, so a user could put something
     338              # like a function or a CommandGenerator in that variable
     339              # instead of a string.
     340              return apply(LazyAction, (var,)+args, kw)
     341          commands = string.split(str(act), '\n')
     342          if len(commands) == 1:
     343              return apply(CommandAction, (commands[0],)+args, kw)
     344          else:
     345              listCmdActions = map(lambda x, args=args, kw=kw:
     346                                   apply(CommandAction, (x,)+args, kw),
     347                                   commands)
     348              return ListAction(listCmdActions)
     349      return None
     350
     351 -def Action(act, *args, **kw):
     352      """A factory for action objects."""
     353      if SCons.Util.is_List(act):
     354          acts = map(lambda a, args=args, kw=kw:
     355                            apply(_do_create_action, (a,)+args, kw),
     356                     act)
     357          acts = filter(None, acts)
     358          if len(acts) == 1:
     359              return acts[0]
     360          else:
     361              return ListAction(acts)
     362      else:
     363          return apply(_do_create_action, (act,)+args, kw)
     364
     365 -class ActionBase:
     366      """Base class for all types of action objects that can be held by
     367      other objects (Builders, Executors, etc.)  This provides the
     368      common methods for manipulating and combining those actions."""
     369
     370 -    def __cmp__(self, other):
     371          return cmp(self.__dict__, other)
     372
     373 -    def genstring(self, target, source, env):
     374          return str(self)
     375
     376 -    def __add__(self, other):
     377          return _actionAppend(self, other)
     378
     379 -    def __radd__(self, other):
     380          return _actionAppend(other, self)
     381
     382 -    def presub_lines(self, env):
     383          # CommandGeneratorAction needs a real environment
     384          # in order to return the proper string here, since
     385          # it may call LazyAction, which looks up a key
     386          # in that env.  So we temporarily remember the env here,
     387          # and CommandGeneratorAction will use this env
     388          # when it calls its _generate method.
     389          self.presub_env = env
     390          lines = string.split(str(self), '\n')
     391          self.presub_env = None      # don't need this any more
     392          return lines
     393
     394 -    def get_executor(self, env, overrides, tlist, slist, executor_kw):
     395          """Return the Executor for this Action."""
     396          return SCons.Executor.Executor(self, env, overrides,
     397                                         tlist, slist, executor_kw)
     398
     399 -class _ActionAction(ActionBase):
     400      """Base class for actions that create output objects."""
     401 -    def __init__(self, strfunction=_null, presub=_null, chdir=None, exitstatfunc=None, **kw):
     402          if not strfunction is _null:
     403              self.strfunction = strfunction
     404          self.presub = presub
     405          self.chdir = chdir
     406          if not exitstatfunc:
     407              exitstatfunc = default_exitstatfunc
     408          self.exitstatfunc = exitstatfunc
     409
     410 -    def print_cmd_line(self, s, target, source, env):
     411          sys.stdout.write(s + "\n")
     412
     413 -    def __call__(self, target, source, env,
     414                                 exitstatfunc=_null,
     415                                 presub=_null,
     416                                 show=_null,
     417                                 execute=_null,
     418                                 chdir=_null):
     419          if not SCons.Util.is_List(target):
     420              target = [target]
     421          if not SCons.Util.is_List(source):
     422              source = [source]
     423
     424          if exitstatfunc is _null: exitstatfunc = self.exitstatfunc
     425          if presub is _null:
     426              presub = self.presub
     427          if presub is _null:
     428              presub = print_actions_presub
     429          if show is _null:  show = print_actions
     430          if execute is _null:  execute = execute_actions
     431          if chdir is _null: chdir = self.chdir
     432          save_cwd = None
     433          if chdir:
     434              save_cwd = os.getcwd()
     435              try:
     436                  chdir = str(chdir.abspath)
     437              except AttributeError:
     438                  if not SCons.Util.is_String(chdir):
     439                      chdir = str(target[0].dir)
     440          if presub:
     441              t = string.join(map(str, target), ' and ')
     442              l = string.join(self.presub_lines(env), '\n  ')
     443              out = "Building %s with action:\n  %s\n" % (t, l)
     444              sys.stdout.write(out)
     445          s = None
     446          if show and self.strfunction:
     447              s = self.strfunction(target, source, env)
     448              if s:
     449                  if chdir:
     450                      s = ('os.chdir(%s)\n' % repr(chdir)) + s
     451                  try:
     452                      get = env.get
     453                  except AttributeError:
     454                      print_func = self.print_cmd_line
     455                  else:
     456                      print_func = get('PRINT_CMD_LINE_FUNC')
     457                      if not print_func:
     458                          print_func = self.print_cmd_line
     459                  print_func(s, target, source, env)
     460          stat = 0
     461          if execute:
     462              if chdir:
     463                  os.chdir(chdir)
     464              try:
     465                  stat = self.execute(target, source, env)
     466                  if isinstance(stat, SCons.Errors.BuildError):
     467                      s = exitstatfunc(stat.status)
     468                      if s:
     469                          stat.status = s
     470                      else:
     471                          stat = s
     472                  else:
     473                      stat = exitstatfunc(stat)
     474              finally:
     475                  if save_cwd:
     476                      os.chdir(save_cwd)
     477          if s and save_cwd:
     478              print_func('os.chdir(%s)' % repr(save_cwd), target, source, env)
     479
     480          return stat
     481
     482
     483 -def _string_from_cmd_list(cmd_list):
     484      """Takes a list of command line arguments and returns a pretty
     485      representation for printing."""
     486      cl = []
     487      for arg in map(str, cmd_list):
     488          if ' ' in arg or '\t' in arg:
     489              arg = '"' + arg + '"'
     490          cl.append(arg)
     491      return string.join(cl)
     492
     493 -class CommandAction(_ActionAction):
     494      """Class for command-execution actions."""
     495 -    def __init__(self, cmd, cmdstr=None, *args, **kw):
     496          # Cmd can actually be a list or a single item; if it's a
     497          # single item it should be the command string to execute; if a
     498          # list then it should be the words of the command string to
     499          # execute.  Only a single command should be executed by this
     500          # object; lists of commands should be handled by embedding
     501          # these objects in a ListAction object (which the Action()
     502          # factory above does).  cmd will be passed to
     503          # Environment.subst_list() for substituting environment
     504          # variables.
     505          if __debug__: logInstanceCreation(self, 'Action.CommandAction')
     506
     507          if not cmdstr is None:
     508              if callable(cmdstr):
     509                  args = (cmdstr,)+args
     510              elif not SCons.Util.is_String(cmdstr):
     511                  raise SCons.Errors.UserError(\
     512                      'Invalid command display variable type. ' \
     513                      'You must either pass a string or a callback which ' \
     514                      'accepts (target, source, env) as parameters.')
     515
     516          apply(_ActionAction.__init__, (self,)+args, kw)
     517          if SCons.Util.is_List(cmd):
     518              if filter(SCons.Util.is_List, cmd):
     519                  raise TypeError, "CommandAction should be given only " \
     520                        "a single command"
     521          self.cmd_list = cmd
     522          self.cmdstr = cmdstr
     523
     524 -    def __str__(self):
     525          if SCons.Util.is_List(self.cmd_list):
     526              return string.join(map(str, self.cmd_list), ' ')
     527          return str(self.cmd_list)
     528
     529 -    def process(self, target, source, env):
     530          result = env.subst_list(self.cmd_list, 0, target, source)
     531          silent = None
     532          ignore = None
     533          while 1:
     534              try: c = result[0][0][0]
     535              except IndexError: c = None
     536              if c == '@': silent = 1
     537              elif c == '-': ignore = 1
     538              else: break
     539              result[0][0] = result[0][0][1:]
     540          try:
     541              if not result[0][0]:
     542                  result[0] = result[0][1:]
     543          except IndexError:
     544              pass
     545          return result, ignore, silent
     546
     547 -    def strfunction(self, target, source, env):
     548          if not self.cmdstr is None:
     549              from SCons.Subst import SUBST_RAW
     550              c = env.subst(self.cmdstr, SUBST_RAW, target, source)
     551              if c:
     552                  return c
     553          cmd_list, ignore, silent = self.process(target, source, env)
     554          if silent:
     555              return ''
     556          return _string_from_cmd_list(cmd_list[0])
     557
     558 -    def execute(self, target, source, env):
     559          """Execute a command action.
     560
     561          This will handle lists of commands as well as individual commands,
     562          because construction variable substitution may turn a single
     563          "command" into a list.  This means that this class can actually
     564          handle lists of commands, even though that's not how we use it
     565          externally.
     566          """
     567          from SCons.Subst import escape_list
     568          import SCons.Util
     569          flatten_sequence = SCons.Util.flatten_sequence
     570          is_String = SCons.Util.is_String
     571          is_List = SCons.Util.is_List
     572
     573          try:
     574              shell = env['SHELL']
     575          except KeyError:
     576              raise SCons.Errors.UserError('Missing SHELL construction variable.')
     577
     578          try:
     579              spawn = env['SPAWN']
     580          except KeyError:
     581              raise SCons.Errors.UserError('Missing SPAWN construction variable.')
     582          else:
     583              if is_String(spawn):
     584                  spawn = env.subst(spawn, raw=1, conv=lambda x: x)
     585
     586          escape = env.get('ESCAPE', lambda x: x)
     587
     588          try:
     589              ENV = env['ENV']
     590          except KeyError:
     591              global default_ENV
     592              if not default_ENV:
     593                  import SCons.Environment
     594                  default_ENV = SCons.Environment.Environment()['ENV']
     595              ENV = default_ENV
     596
     597          # Ensure that the ENV values are all strings:
     598          for key, value in ENV.items():
     599              if not is_String(value):
     600                  if is_List(value):
     601                      # If the value is a list, then we assume it is a
     602                      # path list, because that's a pretty common list-like
     603                      # value to stick in an environment variable:
     604                      value = flatten_sequence(value)
     605                      ENV[key] = string.join(map(str, value), os.pathsep)
     606                  else:
     607                      # If it isn't a string or a list, then we just coerce
     608                      # it to a string, which is the proper way to handle
     609                      # Dir and File instances and will produce something
     610                      # reasonable for just about everything else:
     611                      ENV[key] = str(value)
     612
     613          cmd_list, ignore, silent = self.process(target, map(rfile, source), env)
     614
     615          # Use len() to filter out any "command" that's zero-length.
     616          for cmd_line in filter(len, cmd_list):
     617              # Escape the command line for the interpreter we are using.
     618              cmd_line = escape_list(cmd_line, escape)
     619              result = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
     620              if not ignore and result:
     621                  msg = "Error %s" % result
     622                  return SCons.Errors.BuildError(errstr=msg,
     623                                                 status=result,
     624                                                 action=self,
     625                                                 command=cmd_line)
     626          return 0
     627
     628 -    def get_contents(self, target, source, env):
     629          """Return the signature contents of this action's command line.
     630
     631          This strips $(-$) and everything in between the string,
     632          since those parts don't affect signatures.
     633          """
     634          from SCons.Subst import SUBST_SIG
     635          cmd = self.cmd_list
     636          if SCons.Util.is_List(cmd):
     637              cmd = string.join(map(str, cmd))
     638          else:
     639              cmd = str(cmd)
     640          return env.subst_target_source(cmd, SUBST_SIG, target, source)
     641
     642 -    def get_implicit_deps(self, target, source, env):
     643          icd = env.get('IMPLICIT_COMMAND_DEPENDENCIES', True)
     644          if SCons.Util.is_String(icd) and icd[:1] == '$':
     645              icd = env.subst(icd)
     646          if not icd or icd in ('0', 'None'):
     647              return []
     648          from SCons.Subst import SUBST_SIG
     649          cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, target, source)
     650          res = []
     651          for cmd_line in cmd_list:
     652              if cmd_line:
     653                  d = env.WhereIs(str(cmd_line[0]))
     654                  if d:
     655                      res.append(env.fs.File(d))
     656          return res
     657
     658 -class CommandGeneratorAction(ActionBase):
     659      """Class for command-generator actions."""
     660 -    def __init__(self, generator, *args, **kw):
     661          if __debug__: logInstanceCreation(self, 'Action.CommandGeneratorAction')
     662          self.generator = generator
     663          self.gen_args = args
     664          self.gen_kw = kw
     665
     666 -    def _generate(self, target, source, env, for_signature):
     667          # ensure that target is a list, to make it easier to write
     668          # generator functions:
     669          if not SCons.Util.is_List(target):
     670              target = [target]
     671
     672          ret = self.generator(target=target, source=source, env=env, for_signature=for_signature)
     673          gen_cmd = apply(Action, (ret,)+self.gen_args, self.gen_kw)
     674          if not gen_cmd:
     675              raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
     676          return gen_cmd
     677
     678 -    def __str__(self):
     679          try:
     680              env = self.presub_env
     681          except AttributeError:
     682              env = None
     683          if env is None:
     684              env = SCons.Defaults.DefaultEnvironment()
     685          act = self._generate([], [], env, 1)
     686          return str(act)
     687
     688 -    def genstring(self, target, source, env):
     689          return self._generate(target, source, env, 1).genstring(target, source, env)
     690
     691 -    def __call__(self, target, source, env, exitstatfunc=_null, presub=_null,
     692                   show=_null, execute=_null, chdir=_null):
     693          act = self._generate(target, source, env, 0)
     694          return act(target, source, env, exitstatfunc, presub,
     695                     show, execute, chdir)
     696
     697 -    def get_contents(self, target, source, env):
     698          """Return the signature contents of this action's command line.
     699
     700          This strips $(-$) and everything in between the string,
     701          since those parts don't affect signatures.
     702          """
     703          return self._generate(target, source, env, 1).get_contents(target, source, env)
     704
     705 -    def get_implicit_deps(self, target, source, env):
     706          return self._generate(target, source, env, 1).get_implicit_deps(target, source, env)
     707
     708
     709
     710  # A LazyAction is a kind of hybrid generator and command action for
     711  # strings of the form "$VAR".  These strings normally expand to other
     712  # strings (think "$CCCOM" to "$CC -c -o $TARGET $SOURCE"), but we also
     713  # want to be able to replace them with functions in the construction
     714  # environment.  Consequently, we want lazy evaluation and creation of
     715  # an Action in the case of the function, but that's overkill in the more
     716  # normal case of expansion to other strings.
     717  #
     718  # So we do this with a subclass that's both a generator *and*
     719  # a command action.  The overridden methods all do a quick check
     720  # of the construction variable, and if it's a string we just call
     721  # the corresponding CommandAction method to do the heavy lifting.
     722  # If not, then we call the same-named CommandGeneratorAction method.
     723  # The CommandGeneratorAction methods work by using the overridden
     724  # _generate() method, that is, our own way of handling "generation" of
     725  # an action based on what's in the construction variable.
     726
     727 -class LazyAction(CommandGeneratorAction, CommandAction):
     728
     729 -    def __init__(self, var, *args, **kw):
     730          if __debug__: logInstanceCreation(self, 'Action.LazyAction')
     731          apply(CommandAction.__init__, (self, '$'+var)+args, kw)
     732          self.var = SCons.Util.to_String(var)
     733          self.gen_args = args
     734          self.gen_kw = kw
     735
     736 -    def get_parent_class(self, env):
     737          c = env.get(self.var)
     738          if SCons.Util.is_String(c) and not '\n' in c:
     739              return CommandAction
     740          return CommandGeneratorAction
     741
     742 -    def _generate_cache(self, env):
     743          c = env.get(self.var, '')
     744          gen_cmd = apply(Action, (c,)+self.gen_args, self.gen_kw)
     745          if not gen_cmd:
     746              raise SCons.Errors.UserError("$%s value %s cannot be used to create an Action." % (self.var, repr(c)))
     747          return gen_cmd
     748
     749 -    def _generate(self, target, source, env, for_signature):
     750          return self._generate_cache(env)
     751
     752 -    def __call__(self, target, source, env, *args, **kw):
     753          args = (self, target, source, env) + args
     754          c = self.get_parent_class(env)
     755          return apply(c.__call__, args, kw)
     756
     757 -    def get_contents(self, target, source, env):
     758          c = self.get_parent_class(env)
     759          return c.get_contents(self, target, source, env)
     760
     761
     762
     763 -class FunctionAction(_ActionAction):
     764      """Class for Python function actions."""
     765
     766 -    def __init__(self, execfunction, cmdstr=_null, *args, **kw):
     767          if __debug__: logInstanceCreation(self, 'Action.FunctionAction')
     768
     769          if not cmdstr is _null:
     770              if callable(cmdstr):
     771                  args = (cmdstr,)+args
     772              elif not (cmdstr is None or SCons.Util.is_String(cmdstr)):
     773                  raise SCons.Errors.UserError(\
     774                      'Invalid function display variable type. ' \
     775                      'You must either pass a string or a callback which ' \
     776                      'accepts (target, source, env) as parameters.')
     777
     778          self.execfunction = execfunction
     779          try:
     780              self.funccontents = _callable_contents(execfunction)
     781          except AttributeError:
     782              try:
     783                  # See if execfunction will do the heavy lifting for us.
     784                  self.gc = execfunction.get_contents
     785              except AttributeError:
     786                  # This is weird, just do the best we can.
     787                  self.funccontents = _object_contents(execfunction)
     788
     789          apply(_ActionAction.__init__, (self,)+args, kw)
     790          self.varlist = kw.get('varlist', [])
     791          self.cmdstr = cmdstr
     792
     793 -    def function_name(self):
     794          try:
     795              return self.execfunction.__name__
     796          except AttributeError:
     797              try:
     798                  return self.execfunction.__class__.__name__
     799              except AttributeError:
     800                  return "unknown_python_function"
     801
     802 -    def strfunction(self, target, source, env):
     803          if self.cmdstr is None:
     804              return None
     805          if not self.cmdstr is _null:
     806              from SCons.Subst import SUBST_RAW
     807              c = env.subst(self.cmdstr, SUBST_RAW, target, source)
     808              if c:
     809                  return c
     810          def array(a):
     811              def quote(s):
     812                  return '"' + str(s) + '"'
     813              return '[' + string.join(map(quote, a), ", ") + ']'
     814          try:
     815              strfunc = self.execfunction.strfunction
     816          except AttributeError:
     817              pass
     818          else:
     819              if strfunc is None:
     820                  return None
     821              if callable(strfunc):
     822                  return strfunc(target, source, env)
     823          name = self.function_name()
     824          tstr = array(target)
     825          sstr = array(source)
     826          return "%s(%s, %s)" % (name, tstr, sstr)
     827
     828 -    def __str__(self):
     829          name = self.function_name()
     830          if name == 'ActionCaller':
     831              return str(self.execfunction)
     832          return "%s(target, source, env)" % name
     833
     834 -    def execute(self, target, source, env):
     835          rsources = map(rfile, source)
     836          try:
     837              result = self.execfunction(target=target, source=rsources, env=env)
     838          except EnvironmentError, e:
     839              # If an IOError/OSError happens, raise a BuildError.
     840              # Report the name of the file or directory that caused the
     841              # error, which might be different from the target being built
     842              # (for example, failure to create the directory in which the
     843              # target file will appear).
     844              try: filename = e.filename
     845              except AttributeError: filename = None
     846              result = SCons.Errors.BuildError(node=target,
     847                                               errstr=e.strerror,
     848                                               status=1,
     849                                               filename=filename,
     850                                               action=self,
     851                                               command=self.strfunction(target, source, env))
     852          else:
     853              if result:
     854                  msg = "Error %s" % result
     855                  result = SCons.Errors.BuildError(errstr=msg,
     856                                                   status=result,
     857                                                   action=self,
     858                                                   command=self.strfunction(target, source, env))
     859          return result
     860
     861 -    def get_contents(self, target, source, env):
     862          """Return the signature contents of this callable action."""
     863          try:
     864              contents = self.gc(target, source, env)
     865          except AttributeError:
     866              contents = self.funccontents
     867
     868          return contents + env.subst(string.join(map(lambda v: '${'+v+'}',
     869                                                      self.varlist)))
     870
     871 -    def get_implicit_deps(self, target, source, env):
     872          return []
     873
     874 -class ListAction(ActionBase):
     875      """Class for lists of other actions."""
     876 -    def __init__(self, list):
     877          if __debug__: logInstanceCreation(self, 'Action.ListAction')
     878          def list_of_actions(x):
     879              if isinstance(x, ActionBase):
     880                  return x
     881              return Action(x)
     882          self.list = map(list_of_actions, list)
     883
     884 -    def genstring(self, target, source, env):
     885          return string.join(map(lambda a, t=target, s=source, e=env:
     886                                    a.genstring(t, s, e),
     887                                 self.list),
     888                             '\n')
     889
     890 -    def __str__(self):
     891          return string.join(map(str, self.list), '\n')
     892
     893 -    def presub_lines(self, env):
     894          return SCons.Util.flatten_sequence(
     895              map(lambda a, env=env: a.presub_lines(env), self.list))
     896
     897 -    def get_contents(self, target, source, env):
     898          """Return the signature contents of this action list.
     899
     900          Simple concatenation of the signatures of the elements.
     901          """
     902          return string.join(map(lambda x, t=target, s=source, e=env:
     903                                        x.get_contents(t, s, e),
     904                                 self.list),
     905                             "")
     906
     907 -    def __call__(self, target, source, env, exitstatfunc=_null, presub=_null,
     908                   show=_null, execute=_null, chdir=_null):
     909          for act in self.list:
     910              stat = act(target, source, env, exitstatfunc, presub,
     911                         show, execute, chdir)
     912              if stat:
     913                  return stat
     914          return 0
     915
     916 -    def get_implicit_deps(self, target, source, env):
     917          result = []
     918          for act in self.list:
     919              result.extend(act.get_implicit_deps(target, source, env))
     920          return result
     921
     922 -class ActionCaller:
     923      """A class for delaying calling an Action function with specific
     924      (positional and keyword) arguments until the Action is actually
     925      executed.
     926
     927      This class looks to the rest of the world like a normal Action object,
     928      but what it's really doing is hanging on to the arguments until we
     929      have a target, source and env to use for the expansion.
     930      """
     931 -    def __init__(self, parent, args, kw):
     932          self.parent = parent
     933          self.args = args
     934          self.kw = kw
     935 -    def get_contents(self, target, source, env):
     936          actfunc = self.parent.actfunc
     937          try:
     938              # "self.actfunc" is a function.
     939              contents = str(actfunc.func_code.co_code)
     940          except AttributeError:
     941              # "self.actfunc" is a callable object.
     942              try:
     943                  contents = str(actfunc.__call__.im_func.func_code.co_code)
     944              except AttributeError:
     945                  # No __call__() method, so it might be a builtin
     946                  # or something like that.  Do the best we can.
     947                  contents = str(actfunc)
     948          contents = remove_set_lineno_codes(contents)
     949          return contents
     950 -    def subst(self, s, target, source, env):
     951          # If s is a list, recursively apply subst()
     952          # to every element in the list
     953          if SCons.Util.is_List(s):
     954              result = []
     955              for elem in s:
     956                  result.append(self.subst(elem, target, source, env))
     957              return self.parent.convert(result)
     958
     959          # Special-case hack:  Let a custom function wrapped in an
     960          # ActionCaller get at the environment through which the action
     961          # was called by using this hard-coded value as a special return.
     962          if s == '$__env__':
     963              return env
     964          elif SCons.Util.is_String(s):
     965              return env.subst(s, 1, target, source)
     966          return self.parent.convert(s)
     967 -    def subst_args(self, target, source, env):
     968          return map(lambda x, self=self, t=target, s=source, e=env:
     969                            self.subst(x, t, s, e),
     970                     self.args)
     971 -    def subst_kw(self, target, source, env):
     972          kw = {}
     973          for key in self.kw.keys():
     974              kw[key] = self.subst(self.kw[key], target, source, env)
     975          return kw
     976 -    def __call__(self, target, source, env):
     977          args = self.subst_args(target, source, env)
     978          kw = self.subst_kw(target, source, env)
     979          return apply(self.parent.actfunc, args, kw)
     980 -    def strfunction(self, target, source, env):
     981          args = self.subst_args(target, source, env)
     982          kw = self.subst_kw(target, source, env)
     983          return apply(self.parent.strfunc, args, kw)
     984 -    def __str__(self):
     985          return apply(self.parent.strfunc, self.args, self.kw)
     986
     987 -class ActionFactory:
     988      """A factory class that will wrap up an arbitrary function
     989      as an SCons-executable Action object.
     990
     991      The real heavy lifting here is done by the ActionCaller class.
     992      We just collect the (positional and keyword) arguments that we're
     993      called with and give them to the ActionCaller object we create,
     994      so it can hang onto them until it needs them.
     995      """
     996 -    def __init__(self, actfunc, strfunc, convert=lambda x: x):
     997          self.actfunc = actfunc
     998          self.strfunc = strfunc
     999          self.convert = convert
    1000 -    def __call__(self, *args, **kw):
    1001          ac = ActionCaller(self, args, kw)
    1002          action = Action(ac, strfunction=ac.strfunction)
    1003          return action
    1004