SCons.Script.SConscript'

Package SCons :: Package Script :: Module SConscript' |
[hide private]
[frames] | no frames]

# Source Code for Module SCons.Script.SConscript'

      1  """SCons.Script.SConscript
      2
      3  This module defines the Python API provided to SConscript and SConstruct
      4  files.
      5
      6  """
      7
      8  #
      9  # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation
     10  #
     11  # Permission is hereby granted, free of charge, to any person obtaining
     12  # a copy of this software and associated documentation files (the
     13  # "Software"), to deal in the Software without restriction, including
     14  # without limitation the rights to use, copy, modify, merge, publish,
     15  # distribute, sublicense, and/or sell copies of the Software, and to
     16  # permit persons to whom the Software is furnished to do so, subject to
     17  # the following conditions:
     18  #
     19  # The above copyright notice and this permission notice shall be included
     20  # in all copies or substantial portions of the Software.
     21  #
     22  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
     23  # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
     24  # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
     25  # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
     26  # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
     27  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
     28  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     29  #
     30
     31  __revision__ = "src/engine/SCons/Script/SConscript.py 2725 2008/03/31 12:52:02 knight"
     32
     33  import SCons
     34  import SCons.Action
     35  import SCons.Builder
     36  import SCons.Defaults
     37  import SCons.Environment
     38  import SCons.Errors
     39  import SCons.Node
     40  import SCons.Node.Alias
     41  import SCons.Node.FS
     42  import SCons.Options
     43  import SCons.Platform
     44  import SCons.SConf
     45  import SCons.Script.Main
     46  import SCons.Tool
     47  import SCons.Util
     48
     49  import os
     50  import os.path
     51  import re
     52  import string
     53  import sys
     54  import traceback
     55  import types
     56  import UserList
     57
     58  # The following variables used to live in this module.  Some
     59  # SConscript files out there may have referred to them directly as
     60  # SCons.Script.SConscript.*.  This is now supported by some special
     61  # handling towards the bottom of the SConscript.__init__.py module.
     62  #Arguments = {}
     63  #ArgList = []
     64  #BuildTargets = TargetList()
     65  #CommandLineTargets = []
     66  #DefaultTargets = []
     67
     68 -class SConscriptReturn(Exception):
     69      pass
     70
     71  launch_dir = os.path.abspath(os.curdir)
     72
     73  GlobalDict = None
     74
     75  # global exports set by Export():
     76  global_exports = {}
     77
     78  # chdir flag
     79  sconscript_chdir = 1
     80
     81 -def get_calling_namespaces():
     82      """Return the locals and globals for the function that called
     83      into this module in the current call stack."""
     84      try: 1/0
     85      except ZeroDivisionError: frame = sys.exc_info()[2].tb_frame
     86
     87      # Find the first frame that *isn't* from this file.  This means
     88      # that we expect all of the SCons frames that implement an Export()
     89      # or SConscript() call to be in this file, so that we can identify
     90      # the first non-Script.SConscript frame as the user's local calling
     91      # environment, and the locals and globals dictionaries from that
     92      # frame as the calling namespaces.  See the comment below preceding
     93      # the DefaultEnvironmentCall block for even more explanation.
     94      while frame.f_globals.get("__name__") == __name__:
     95          frame = frame.f_back
     96
     97      return frame.f_locals, frame.f_globals
     98
     99
    100 -def compute_exports(exports):
    101      """Compute a dictionary of exports given one of the parameters
    102      to the Export() function or the exports argument to SConscript()."""
    103
    104      loc, glob = get_calling_namespaces()
    105
    106      retval = {}
    107      try:
    108          for export in exports:
    109              if SCons.Util.is_Dict(export):
    110                  retval.update(export)
    111              else:
    112                  try:
    113                      retval[export] = loc[export]
    114                  except KeyError:
    115                      retval[export] = glob[export]
    116      except KeyError, x:
    117          raise SCons.Errors.UserError, "Export of non-existent variable '%s'"%x
    118
    119      return retval
    120
    121 -class Frame:
    122      """A frame on the SConstruct/SConscript call stack"""
    123 -    def __init__(self, fs, exports, sconscript):
    124          self.globals = BuildDefaultGlobals()
    125          self.retval = None
    126          self.prev_dir = fs.getcwd()
    127          self.exports = compute_exports(exports)  # exports from the calling SConscript
    128          # make sure the sconscript attr is a Node.
    129          if isinstance(sconscript, SCons.Node.Node):
    130              self.sconscript = sconscript
    131          elif sconscript == '-':
    132              self.sconscript = None
    133          else:
    134              self.sconscript = fs.File(str(sconscript))
    135
    136  # the SConstruct/SConscript call stack:
    137  call_stack = []
    138
    139  # For documentation on the methods in this file, see the scons man-page
    140
    141 -def Return(*vars, **kw):
    142      retval = []
    143      try:
    144          for var in vars:
    145              for v in string.split(var):
    146                  retval.append(call_stack[-1].globals[v])
    147      except KeyError, x:
    148          raise SCons.Errors.UserError, "Return of non-existent variable '%s'"%x
    149
    150      if len(retval) == 1:
    151          call_stack[-1].retval = retval[0]
    152      else:
    153          call_stack[-1].retval = tuple(retval)
    154
    155      stop = kw.get('stop', True)
    156
    157      if stop:
    158          raise SConscriptReturn
    159
    160
    161  stack_bottom = '% Stack boTTom %' # hard to define a variable w/this name :)
    162
    163 -def _SConscript(fs, *files, **kw):
    164      top = fs.Top
    165      sd = fs.SConstruct_dir.rdir()
    166      exports = kw.get('exports', [])
    167
    168      # evaluate each SConscript file
    169      results = []
    170      for fn in files:
    171          call_stack.append(Frame(fs, exports, fn))
    172          old_sys_path = sys.path
    173          try:
    174              SCons.Script.sconscript_reading = SCons.Script.sconscript_reading + 1
    175              if fn == "-":
    176                  exec sys.stdin in call_stack[-1].globals
    177              else:
    178                  if isinstance(fn, SCons.Node.Node):
    179                      f = fn
    180                  else:
    181                      f = fs.File(str(fn))
    182                  _file_ = None
    183
    184                  # Change directory to the top of the source
    185                  # tree to make sure the os's cwd and the cwd of
    186                  # fs match so we can open the SConscript.
    187                  fs.chdir(top, change_os_dir=1)
    188                  if f.rexists():
    189                      _file_ = open(f.rfile().get_abspath(), "r")
    190                  elif f.has_src_builder():
    191                      # The SConscript file apparently exists in a source
    192                      # code management system.  Build it, but then clear
    193                      # the builder so that it doesn't get built *again*
    194                      # during the actual build phase.
    195                      f.build()
    196                      f.built()
    197                      f.builder_set(None)
    198                      if f.exists():
    199                          _file_ = open(f.get_abspath(), "r")
    200                  if _file_:
    201                      # Chdir to the SConscript directory.  Use a path
    202                      # name relative to the SConstruct file so that if
    203                      # we're using the -f option, we're essentially
    204                      # creating a parallel SConscript directory structure
    205                      # in our local directory tree.
    206                      #
    207                      # XXX This is broken for multiple-repository cases
    208                      # where the SConstruct and SConscript files might be
    209                      # in different Repositories.  For now, cross that
    210                      # bridge when someone comes to it.
    211                      try:
    212                          src_dir = kw['src_dir']
    213                      except KeyError:
    214                          ldir = fs.Dir(f.dir.get_path(sd))
    215                      else:
    216                          ldir = fs.Dir(src_dir)
    217                          if not ldir.is_under(f.dir):
    218                              # They specified a source directory, but
    219                              # it's above the SConscript directory.
    220                              # Do the sensible thing and just use the
    221                              # SConcript directory.
    222                              ldir = fs.Dir(f.dir.get_path(sd))
    223                      try:
    224                          fs.chdir(ldir, change_os_dir=sconscript_chdir)
    225                      except OSError:
    226                          # There was no local directory, so we should be
    227                          # able to chdir to the Repository directory.
    228                          # Note that we do this directly, not through
    229                          # fs.chdir(), because we still need to
    230                          # interpret the stuff within the SConscript file
    231                          # relative to where we are logically.
    232                          fs.chdir(ldir, change_os_dir=0)
    233                          # TODO Not sure how to handle src_dir here
    234                          os.chdir(f.rfile().dir.get_abspath())
    235
    236                      # Append the SConscript directory to the beginning
    237                      # of sys.path so Python modules in the SConscript
    238                      # directory can be easily imported.
    239                      sys.path = [ f.dir.get_abspath() ] + sys.path
    240
    241                      # This is the magic line that actually reads up
    242                      # and executes the stuff in the SConscript file.
    243                      # The locals for this frame contain the special
    244                      # bottom-of-the-stack marker so that any
    245                      # exceptions that occur when processing this
    246                      # SConscript can base the printed frames at this
    247                      # level and not show SCons internals as well.
    248                      call_stack[-1].globals.update({stack_bottom:1})
    249                      old_file = call_stack[-1].globals.get('__file__')
    250                      try:
    251                          del call_stack[-1].globals['__file__']
    252                      except KeyError:
    253                          pass
    254                      try:
    255                          try:
    256                              exec _file_ in call_stack[-1].globals
    257                          except SConscriptReturn:
    258                              pass
    259                      finally:
    260                          if old_file is not None:
    261                              call_stack[-1].globals.update({__file__:old_file})
    262                  else:
    263                      SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning,
    264                               "Ignoring missing SConscript '%s'" % f.path)
    265
    266          finally:
    267              SCons.Script.sconscript_reading = SCons.Script.sconscript_reading - 1
    268              sys.path = old_sys_path
    269              frame = call_stack.pop()
    270              try:
    271                  fs.chdir(frame.prev_dir, change_os_dir=sconscript_chdir)
    272              except OSError:
    273                  # There was no local directory, so chdir to the
    274                  # Repository directory.  Like above, we do this
    275                  # directly.
    276                  fs.chdir(frame.prev_dir, change_os_dir=0)
    277                  rdir = frame.prev_dir.rdir()
    278                  rdir._create()  # Make sure there's a directory there.
    279                  os.chdir(rdir.get_abspath())
    280
    281              results.append(frame.retval)
    282
    283      # if we only have one script, don't return a tuple
    284      if len(results) == 1:
    285          return results[0]
    286      else:
    287          return tuple(results)
    288
    289 -def SConscript_exception(file=sys.stderr):
    290      """Print an exception stack trace just for the SConscript file(s).
    291      This will show users who have Python errors where the problem is,
    292      without cluttering the output with all of the internal calls leading
    293      up to where we exec the SConscript."""
    294      exc_type, exc_value, exc_tb = sys.exc_info()
    295      tb = exc_tb
    296      while tb and not tb.tb_frame.f_locals.has_key(stack_bottom):
    297          tb = tb.tb_next
    298      if not tb:
    299          # We did not find our exec statement, so this was actually a bug
    300          # in SCons itself.  Show the whole stack.
    301          tb = exc_tb
    302      stack = traceback.extract_tb(tb)
    303      try:
    304          type = exc_type.__name__
    305      except AttributeError:
    306          type = str(exc_type)
    307          if type[:11] == "exceptions.":
    308              type = type[11:]
    309      file.write('%s: %s:\n' % (type, exc_value))
    310      for fname, line, func, text in stack:
    311          file.write('  File "%s", line %d:\n' % (fname, line))
    312          file.write('    %s\n' % text)
    313
    314 -def annotate(node):
    315      """Annotate a node with the stack frame describing the
    316      SConscript file and line number that created it."""
    317      tb = sys.exc_info()[2]
    318      while tb and not tb.tb_frame.f_locals.has_key(stack_bottom):
    319          tb = tb.tb_next
    320      if not tb:
    321          # We did not find any exec of an SConscript file: what?!
    322          raise SCons.Errors.InternalError, "could not find SConscript stack frame"
    323      node.creator = traceback.extract_stack(tb)[0]
    324
    325  # The following line would cause each Node to be annotated using the
    326  # above function.  Unfortunately, this is a *huge* performance hit, so
    327  # leave this disabled until we find a more efficient mechanism.
    328  #SCons.Node.Annotate = annotate
    329
    330 -class SConsEnvironment(SCons.Environment.Base):
    331      """An Environment subclass that contains all of the methods that
    332      are particular to the wrapper SCons interface and which aren't
    333      (or shouldn't be) part of the build engine itself.
    334
    335      Note that not all of the methods of this class have corresponding
    336      global functions, there are some private methods.
    337      """
    338
    339      #
    340      # Private methods of an SConsEnvironment.
    341      #
    342 -    def _exceeds_version(self, major, minor, v_major, v_minor):
    343          """Return 1 if 'major' and 'minor' are greater than the version
    344          in 'v_major' and 'v_minor', and 0 otherwise."""
    345          return (major > v_major or (major == v_major and minor > v_minor))
    346
    347 -    def _get_major_minor_revision(self, version_string):
    348          """Split a version string into major, minor and (optionally)
    349          revision parts.
    350
    351          This is complicated by the fact that a version string can be
    352          something like 3.2b1."""
    353          version = string.split(string.split(version_string, ' ')[0], '.')
    354          v_major = int(version[0])
    355          v_minor = int(re.match('\d+', version[1]).group())
    356          if len(version) >= 3:
    357              v_revision = int(re.match('\d+', version[2]).group())
    358          else:
    359              v_revision = 0
    360          return v_major, v_minor, v_revision
    361
    362 -    def _get_SConscript_filenames(self, ls, kw):
    363          """
    364          Convert the parameters passed to # SConscript() calls into a list
    365          of files and export variables.  If the parameters are invalid,
    366          throws SCons.Errors.UserError. Returns a tuple (l, e) where l
    367          is a list of SConscript filenames and e is a list of exports.
    368          """
    369          exports = []
    370
    371          if len(ls) == 0:
    372              try:
    373                  dirs = kw["dirs"]
    374              except KeyError:
    375                  raise SCons.Errors.UserError, \
    376                        "Invalid SConscript usage - no parameters"
    377
    378              if not SCons.Util.is_List(dirs):
    379                  dirs = [ dirs ]
    380              dirs = map(str, dirs)
    381
    382              name = kw.get('name', 'SConscript')
    383
    384              files = map(lambda n, name = name: os.path.join(n, name), dirs)
    385
    386          elif len(ls) == 1:
    387
    388              files = ls[0]
    389
    390          elif len(ls) == 2:
    391
    392              files   = ls[0]
    393              exports = self.Split(ls[1])
    394
    395          else:
    396
    397              raise SCons.Errors.UserError, \
    398                    "Invalid SConscript() usage - too many arguments"
    399
    400          if not SCons.Util.is_List(files):
    401              files = [ files ]
    402
    403          if kw.get('exports'):
    404              exports.extend(self.Split(kw['exports']))
    405
    406          variant_dir = kw.get('variant_dir') or kw.get('build_dir')
    407          if variant_dir:
    408              if len(files) != 1:
    409                  raise SCons.Errors.UserError, \
    410                      "Invalid SConscript() usage - can only specify one SConscript with a variant_dir"
    411              duplicate = kw.get('duplicate', 1)
    412              src_dir = kw.get('src_dir')
    413              if not src_dir:
    414                  src_dir, fname = os.path.split(str(files[0]))
    415                  files = [os.path.join(str(variant_dir), fname)]
    416              else:
    417                  if not isinstance(src_dir, SCons.Node.Node):
    418                      src_dir = self.fs.Dir(src_dir)
    419                  fn = files[0]
    420                  if not isinstance(fn, SCons.Node.Node):
    421                      fn = self.fs.File(fn)
    422                  if fn.is_under(src_dir):
    423                      # Get path relative to the source directory.
    424                      fname = fn.get_path(src_dir)
    425                      files = [os.path.join(str(variant_dir), fname)]
    426                  else:
    427                      files = [fn.abspath]
    428                  kw['src_dir'] = variant_dir
    429              self.fs.VariantDir(variant_dir, src_dir, duplicate)
    430
    431          return (files, exports)
    432
    433      #
    434      # Public methods of an SConsEnvironment.  These get
    435      # entry points in the global name space so they can be called
    436      # as global functions.
    437      #
    438
    439 -    def Configure(self, *args, **kw):
    440          if not SCons.Script.sconscript_reading:
    441              raise SCons.Errors.UserError, "Calling Configure from Builders is not supported."
    442          kw['_depth'] = kw.get('_depth', 0) + 1
    443          return apply(SCons.Environment.Base.Configure, (self,)+args, kw)
    444
    445 -    def Default(self, *targets):
    446          SCons.Script._Set_Default_Targets(self, targets)
    447
    448 -    def EnsureSConsVersion(self, major, minor, revision=0):
    449          """Exit abnormally if the SCons version is not late enough."""
    450          scons_ver = self._get_major_minor_revision(SCons.__version__)
    451          if scons_ver < (major, minor, revision):
    452              if revision:
    453                  scons_ver_string = '%d.%d.%d' % (major, minor, revision)
    454              else:
    455                  scons_ver_string = '%d.%d' % (major, minor)
    456              print "SCons %s or greater required, but you have SCons %s" % \
    457                    (scons_ver_string, SCons.__version__)
    458              sys.exit(2)
    459
    460 -    def EnsurePythonVersion(self, major, minor):
    461          """Exit abnormally if the Python version is not late enough."""
    462          try:
    463              v_major, v_minor, v_micro, release, serial = sys.version_info
    464              python_ver = (v_major, v_minor)
    465          except AttributeError:
    466              python_ver = self._get_major_minor_revision(sys.version)[:2]
    467          if python_ver < (major, minor):
    468              v = string.split(sys.version, " ", 1)[0]
    469              print "Python %d.%d or greater required, but you have Python %s" %(major,minor,v)
    470              sys.exit(2)
    471
    472 -    def Exit(self, value=0):
    473          sys.exit(value)
    474
    475 -    def Export(self, *vars):
    476          for var in vars:
    477              global_exports.update(compute_exports(self.Split(var)))
    478
    479 -    def GetLaunchDir(self):
    480          global launch_dir
    481          return launch_dir
    482
    483 -    def GetOption(self, name):
    484          name = self.subst(name)
    485          return SCons.Script.Main.GetOption(name)
    486
    487 -    def Help(self, text):
    488          text = self.subst(text, raw=1)
    489          SCons.Script.HelpFunction(text)
    490
    491 -    def Import(self, *vars):
    492          try:
    493              frame = call_stack[-1]
    494              globals = frame.globals
    495              exports = frame.exports
    496              for var in vars:
    497                  var = self.Split(var)
    498                  for v in var:
    499                      if v == '*':
    500                          globals.update(global_exports)
    501                          globals.update(exports)
    502                      else:
    503                          if exports.has_key(v):
    504                              globals[v] = exports[v]
    505                          else:
    506                              globals[v] = global_exports[v]
    507          except KeyError,x:
    508              raise SCons.Errors.UserError, "Import of non-existent variable '%s'"%x
    509
    510 -    def SConscript(self, *ls, **kw):
    511          def subst_element(x, subst=self.subst):
    512              if SCons.Util.is_List(x):
    513                  x = map(subst, x)
    514              else:
    515                  x = subst(x)
    516              return x
    517          ls = map(subst_element, ls)
    518          subst_kw = {}
    519          for key, val in kw.items():
    520              if SCons.Util.is_String(val):
    521                  val = self.subst(val)
    522              elif SCons.Util.is_List(val):
    523                  result = []
    524                  for v in val:
    525                      if SCons.Util.is_String(v):
    526                          v = self.subst(v)
    527                      result.append(v)
    528                  val = result
    529              subst_kw[key] = val
    530
    531          files, exports = self._get_SConscript_filenames(ls, subst_kw)
    532          subst_kw['exports'] = exports
    533          return apply(_SConscript, [self.fs,] + files, subst_kw)
    534
    535 -    def SConscriptChdir(self, flag):
    536          global sconscript_chdir
    537          sconscript_chdir = flag
    538
    539 -    def SetOption(self, name, value):
    540          name = self.subst(name)
    541          SCons.Script.Main.SetOption(name, value)
    542
    543  #
    544  #
    545  #
    546  SCons.Environment.Environment = SConsEnvironment
    547
    548 -def Configure(*args, **kw):
    549      if not SCons.Script.sconscript_reading:
    550          raise SCons.Errors.UserError, "Calling Configure from Builders is not supported."
    551      kw['_depth'] = 1
    552      return apply(SCons.SConf.SConf, args, kw)
    553
    554  # It's very important that the DefaultEnvironmentCall() class stay in this
    555  # file, with the get_calling_namespaces() function, the compute_exports()
    556  # function, the Frame class and the SConsEnvironment.Export() method.
    557  # These things make up the calling stack leading up to the actual global
    558  # Export() or SConscript() call that the user issued.  We want to allow
    559  # users to export local variables that they define, like so:
    560  #
    561  #       def func():
    562  #           x = 1
    563  #           Export('x')
    564  #
    565  # To support this, the get_calling_namespaces() function assumes that
    566  # the *first* stack frame that's not from this file is the local frame
    567  # for the Export() or SConscript() call.
    568
    569  _DefaultEnvironmentProxy = None
    570
    571 -def get_DefaultEnvironmentProxy():
    572      global _DefaultEnvironmentProxy
    573      if not _DefaultEnvironmentProxy:
    574          default_env = SCons.Defaults.DefaultEnvironment()
    575          _DefaultEnvironmentProxy = SCons.Environment.NoSubstitutionProxy(default_env)
    576      return _DefaultEnvironmentProxy
    577
    578 -class DefaultEnvironmentCall:
    579      """A class that implements "global function" calls of
    580      Environment methods by fetching the specified method from the
    581      DefaultEnvironment's class.  Note that this uses an intermediate
    582      proxy class instead of calling the DefaultEnvironment method
    583      directly so that the proxy can override the subst() method and
    584      thereby prevent expansion of construction variables (since from
    585      the user's point of view this was called as a global function,
    586      with no associated construction environment)."""
    587 -    def __init__(self, method_name, subst=0):
    588          self.method_name = method_name
    589          if subst:
    590              self.factory = SCons.Defaults.DefaultEnvironment
    591          else:
    592              self.factory = get_DefaultEnvironmentProxy
    593 -    def __call__(self, *args, **kw):
    594          env = self.factory()
    595          method = getattr(env, self.method_name)
    596          return apply(method, args, kw)
    597
    598
    599 -def BuildDefaultGlobals():
    600      """
    601      Create a dictionary containing all the default globals for
    602      SConstruct and SConscript files.
    603      """
    604
    605      global GlobalDict
    606      if GlobalDict is None:
    607          GlobalDict = {}
    608
    609          import SCons.Script
    610          d = SCons.Script.__dict__
    611          def not_a_module(m, d=d, mtype=type(SCons.Script)):
    612               return type(d[m]) != mtype
    613          for m in filter(not_a_module, dir(SCons.Script)):
    614               GlobalDict[m] = d[m]
    615
    616      return GlobalDict.copy()
    617