SCons.Scanner

Package SCons :: Package Scanner |
[hide private]
[frames] | no frames]

# Source Code for Package SCons.Scanner

      1  """SCons.Scanner
      2
      3  The Scanner package for the SCons software construction utility.
      4
      5  """
      6
      7  #
      8  # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation
      9  #
     10  # Permission is hereby granted, free of charge, to any person obtaining
     11  # a copy of this software and associated documentation files (the
     12  # "Software"), to deal in the Software without restriction, including
     13  # without limitation the rights to use, copy, modify, merge, publish,
     14  # distribute, sublicense, and/or sell copies of the Software, and to
     15  # permit persons to whom the Software is furnished to do so, subject to
     16  # the following conditions:
     17  #
     18  # The above copyright notice and this permission notice shall be included
     19  # in all copies or substantial portions of the Software.
     20  #
     21  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
     22  # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
     23  # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
     24  # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
     25  # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
     26  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
     27  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     28  #
     29
     30  __revision__ = "src/engine/SCons/Scanner/__init__.py 2725 2008/03/31 12:52:02 knight"
     31
     32  import re
     33  import string
     34
     35  import SCons.Node.FS
     36  import SCons.Util
     37
     38
     39 -class _Null:
     40      pass
     41
     42  # This is used instead of None as a default argument value so None can be
     43  # used as an actual argument value.
     44  _null = _Null
     45
     46 -def Scanner(function, *args, **kw):
     47      """
     48      Public interface factory function for creating different types
     49      of Scanners based on the different types of "functions" that may
     50      be supplied.
     51
     52      TODO:  Deprecate this some day.  We've moved the functionality
     53      inside the Base class and really don't need this factory function
     54      any more.  It was, however, used by some of our Tool modules, so
     55      the call probably ended up in various people's custom modules
     56      patterned on SCons code.
     57      """
     58      if SCons.Util.is_Dict(function):
     59          return apply(Selector, (function,) + args, kw)
     60      else:
     61          return apply(Base, (function,) + args, kw)
     62
     63
     64
     65 -class FindPathDirs:
     66      """A class to bind a specific *PATH variable name to a function that
     67      will return all of the *path directories."""
     68 -    def __init__(self, variable):
     69          self.variable = variable
     70 -    def __call__(self, env, dir=None, target=None, source=None, argument=None):
     71          import SCons.PathList
     72          try:
     73              path = env[self.variable]
     74          except KeyError:
     75              return ()
     76
     77          dir = dir or env.fs._cwd
     78          path = SCons.PathList.PathList(path).subst_path(env, target, source)
     79          return tuple(dir.Rfindalldirs(path))
     80
     81
     82
     83 -class Base:
     84      """
     85      The base class for dependency scanners.  This implements
     86      straightforward, single-pass scanning of a single file.
     87      """
     88
     89 -    def __init__(self,
     90                   function,
     91                   name = "NONE",
     92                   argument = _null,
     93                   skeys = _null,
     94                   path_function = None,
     95                   node_class = SCons.Node.FS.Entry,
     96                   node_factory = None,
     97                   scan_check = None,
     98                   recursive = None):
     99          """
    100          Construct a new scanner object given a scanner function.
    101
    102          'function' - a scanner function taking two or three
    103          arguments and returning a list of strings.
    104
    105          'name' - a name for identifying this scanner object.
    106
    107          'argument' - an optional argument that, if specified, will be
    108          passed to both the scanner function and the path_function.
    109
    110          'skeys' - an optional list argument that can be used to determine
    111          which scanner should be used for a given Node. In the case of File
    112          nodes, for example, the 'skeys' would be file suffixes.
    113
    114          'path_function' - a function that takes four or five arguments
    115          (a construction environment, Node for the directory containing
    116          the SConscript file that defined the primary target, list of
    117          target nodes, list of source nodes, and optional argument for
    118          this instance) and returns a tuple of the directories that can
    119          be searched for implicit dependency files.  May also return a
    120          callable() which is called with no args and returns the tuple
    121          (supporting Bindable class).
    122
    123          'node_class' - the class of Nodes which this scan will return.
    124          If node_class is None, then this scanner will not enforce any
    125          Node conversion and will return the raw results from the
    126          underlying scanner function.
    127
    128          'node_factory' - the factory function to be called to translate
    129          the raw results returned by the scanner function into the
    130          expected node_class objects.
    131
    132          'scan_check' - a function to be called to first check whether
    133          this node really needs to be scanned.
    134
    135          'recursive' - specifies that this scanner should be invoked
    136          recursively on all of the implicit dependencies it returns
    137          (the canonical example being #include lines in C source files).
    138          May be a callable, which will be called to filter the list
    139          of nodes found to select a subset for recursive scanning
    140          (the canonical example being only recursively scanning
    141          subdirectories within a directory).
    142
    143          The scanner function's first argument will be a Node that should
    144          be scanned for dependencies, the second argument will be an
    145          Environment object, the third argument will be the tuple of paths
    146          returned by the path_function, and the fourth argument will be
    147          the value passed into 'argument', and the returned list should
    148          contain the Nodes for all the direct dependencies of the file.
    149
    150          Examples:
    151
    152          s = Scanner(my_scanner_function)
    153
    154          s = Scanner(function = my_scanner_function)
    155
    156          s = Scanner(function = my_scanner_function, argument = 'foo')
    157
    158          """
    159
    160          # Note: this class could easily work with scanner functions that take
    161          # something other than a filename as an argument (e.g. a database
    162          # node) and a dependencies list that aren't file names. All that
    163          # would need to be changed is the documentation.
    164
    165          self.function = function
    166          self.path_function = path_function
    167          self.name = name
    168          self.argument = argument
    169
    170          if skeys is _null:
    171              if SCons.Util.is_Dict(function):
    172                  skeys = function.keys()
    173              else:
    174                  skeys = []
    175          self.skeys = skeys
    176
    177          self.node_class = node_class
    178          self.node_factory = node_factory
    179          self.scan_check = scan_check
    180          if callable(recursive):
    181              self.recurse_nodes = recursive
    182          elif recursive:
    183              self.recurse_nodes = self._recurse_all_nodes
    184          else:
    185              self.recurse_nodes = self._recurse_no_nodes
    186
    187 -    def path(self, env, dir=None, target=None, source=None):
    188          if not self.path_function:
    189              return ()
    190          if not self.argument is _null:
    191              return self.path_function(env, dir, target, source, self.argument)
    192          else:
    193              return self.path_function(env, dir, target, source)
    194
    195 -    def __call__(self, node, env, path = ()):
    196          """
    197          This method scans a single object. 'node' is the node
    198          that will be passed to the scanner function, and 'env' is the
    199          environment that will be passed to the scanner function. A list of
    200          direct dependency nodes for the specified node will be returned.
    201          """
    202          if self.scan_check and not self.scan_check(node, env):
    203              return []
    204
    205          self = self.select(node)
    206
    207          if not self.argument is _null:
    208              list = self.function(node, env, path, self.argument)
    209          else:
    210              list = self.function(node, env, path)
    211
    212          kw = {}
    213          if hasattr(node, 'dir'):
    214              kw['directory'] = node.dir
    215          node_factory = env.get_factory(self.node_factory)
    216          nodes = []
    217          for l in list:
    218              if self.node_class and not isinstance(l, self.node_class):
    219                  l = apply(node_factory, (l,), kw)
    220              nodes.append(l)
    221          return nodes
    222
    223 -    def __cmp__(self, other):
    224          try:
    225              return cmp(self.__dict__, other.__dict__)
    226          except AttributeError:
    227              # other probably doesn't have a __dict__
    228              return cmp(self.__dict__, other)
    229
    230 -    def __hash__(self):
    231          return id(self)
    232
    233 -    def __str__(self):
    234          return self.name
    235
    236 -    def add_skey(self, skey):
    237          """Add a skey to the list of skeys"""
    238          self.skeys.append(skey)
    239
    240 -    def get_skeys(self, env=None):
    241          if env and SCons.Util.is_String(self.skeys):
    242              return env.subst_list(self.skeys)[0]
    243          return self.skeys
    244
    245 -    def select(self, node):
    246          if SCons.Util.is_Dict(self.function):
    247              key = node.scanner_key()
    248              try:
    249                  return self.function[key]
    250              except KeyError:
    251                  return None
    252          else:
    253              return self
    254
    255 -    def _recurse_all_nodes(self, nodes):
    256          return nodes
    257
    258 -    def _recurse_no_nodes(self, nodes):
    259          return []
    260
    261      recurse_nodes = _recurse_no_nodes
    262
    263 -    def add_scanner(self, skey, scanner):
    264          self.function[skey] = scanner
    265          self.add_skey(skey)
    266
    267
    268 -class Selector(Base):
    269      """
    270      A class for selecting a more specific scanner based on the
    271      scanner_key() (suffix) for a specific Node.
    272
    273      TODO:  This functionality has been moved into the inner workings of
    274      the Base class, and this class will be deprecated at some point.
    275      (It was never exposed directly as part of the public interface,
    276      although it is used by the Scanner() factory function that was
    277      used by various Tool modules and therefore was likely a template
    278      for custom modules that may be out there.)
    279      """
    280 -    def __init__(self, dict, *args, **kw):
    281          apply(Base.__init__, (self, None,)+args, kw)
    282          self.dict = dict
    283          self.skeys = dict.keys()
    284
    285 -    def __call__(self, node, env, path = ()):
    286          return self.select(node)(node, env, path)
    287
    288 -    def select(self, node):
    289          try:
    290              return self.dict[node.scanner_key()]
    291          except KeyError:
    292              return None
    293
    294 -    def add_scanner(self, skey, scanner):
    295          self.dict[skey] = scanner
    296          self.add_skey(skey)
    297
    298
    299 -class Current(Base):
    300      """
    301      A class for scanning files that are source files (have no builder)
    302      or are derived files and are current (which implies that they exist,
    303      either locally or in a repository).
    304      """
    305
    306 -    def __init__(self, *args, **kw):
    307          def current_check(node, env):
    308              return not node.has_builder() or node.is_up_to_date()
    309          kw['scan_check'] = current_check
    310          apply(Base.__init__, (self,) + args, kw)
    311
    312 -class Classic(Current):
    313      """
    314      A Scanner subclass to contain the common logic for classic CPP-style
    315      include scanning, but which can be customized to use different
    316      regular expressions to find the includes.
    317
    318      Note that in order for this to work "out of the box" (without
    319      overriding the find_include() and sort_key() methods), the regular
    320      expression passed to the constructor must return the name of the
    321      include file in group 0.
    322      """
    323
    324 -    def __init__(self, name, suffixes, path_variable, regex, *args, **kw):
    325
    326          self.cre = re.compile(regex, re.M)
    327
    328          def _scan(node, env, path=(), self=self):
    329              node = node.rfile()
    330              if not node.exists():
    331                  return []
    332              return self.scan(node, path)
    333
    334          kw['function'] = _scan
    335          kw['path_function'] = FindPathDirs(path_variable)
    336          kw['recursive'] = 1
    337          kw['skeys'] = suffixes
    338          kw['name'] = name
    339
    340          apply(Current.__init__, (self,) + args, kw)
    341
    342 -    def find_include(self, include, source_dir, path):
    343          n = SCons.Node.FS.find_file(include, (source_dir,) + tuple(path))
    344          return n, include
    345
    346 -    def sort_key(self, include):
    347          return SCons.Node.FS._my_normcase(include)
    348
    349 -    def find_include_names(self, node):
    350          return self.cre.findall(node.get_contents())
    351
    352 -    def scan(self, node, path=()):
    353
    354          # cache the includes list in node so we only scan it once:
    355          if node.includes != None:
    356              includes = node.includes
    357          else:
    358              includes = self.find_include_names (node)
    359              node.includes = includes
    360
    361          # This is a hand-coded DSU (decorate-sort-undecorate, or
    362          # Schwartzian transform) pattern.  The sort key is the raw name
    363          # of the file as specifed on the #include line (including the
    364          # " or <, since that may affect what file is found), which lets
    365          # us keep the sort order constant regardless of whether the file
    366          # is actually found in a Repository or locally.
    367          nodes = []
    368          source_dir = node.get_dir()
    369          if callable(path):
    370              path = path()
    371          for include in includes:
    372              n, i = self.find_include(include, source_dir, path)
    373
    374              if n is None:
    375                  SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
    376                                      "No dependency generated for file: %s (included from: %s) -- file not found" % (i, node))
    377              else:
    378                  sortkey = self.sort_key(include)
    379                  nodes.append((sortkey, n))
    380
    381          nodes.sort()
    382          nodes = map(lambda pair: pair[1], nodes)
    383          return nodes
    384
    385 -class ClassicCPP(Classic):
    386      """
    387      A Classic Scanner subclass which takes into account the type of
    388      bracketing used to include the file, and uses classic CPP rules
    389      for searching for the files based on the bracketing.
    390
    391      Note that in order for this to work, the regular expression passed
    392      to the constructor must return the leading bracket in group 0, and
    393      the contained filename in group 1.
    394      """
    395 -    def find_include(self, include, source_dir, path):
    396          if include[0] == '"':
    397              paths = (source_dir,) + tuple(path)
    398          else:
    399              paths = tuple(path) + (source_dir,)
    400
    401          n = SCons.Node.FS.find_file(include[1], paths)
    402
    403          return n, include[1]
    404
    405 -    def sort_key(self, include):
    406          return SCons.Node.FS._my_normcase(string.join(include))
    407