SCons.Script.Interactive

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

# Source Code for Module SCons.Script.Interactive

      1  #
      2  # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation
      3  #
      4  # Permission is hereby granted, free of charge, to any person obtaining
      5  # a copy of this software and associated documentation files (the
      6  # "Software"), to deal in the Software without restriction, including
      7  # without limitation the rights to use, copy, modify, merge, publish,
      8  # distribute, sublicense, and/or sell copies of the Software, and to
      9  # permit persons to whom the Software is furnished to do so, subject to
     10  # the following conditions:
     11  #
     12  # The above copyright notice and this permission notice shall be included
     13  # in all copies or substantial portions of the Software.
     14  #
     15  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
     16  # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
     17  # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
     18  # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
     19  # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
     20  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
     21  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     22  #
     23
     24  __revision__ = "src/engine/SCons/Script/Interactive.py 2725 2008/03/31 12:52:02 knight"
     25
     26  __doc__ = """
     27  SCons interactive mode
     28  """
     29
     30  # TODO:
     31  #
     32  # This has the potential to grow into something with a really big life
     33  # of its own, which might or might not be a good thing.  Nevertheless,
     34  # here are some enhancements that will probably be requested some day
     35  # and are worth keeping in mind (assuming this takes off):
     36  #
     37  # - A command to re-read / re-load the SConscript files.  This may
     38  #   involve allowing people to specify command-line options (e.g. -f,
     39  #   -I, --no-site-dir) that affect how the SConscript files are read.
     40  #
     41  # - Additional command-line options on the "build" command.
     42  #
     43  #   Of the supported options that seemed to make sense (after a quick
     44  #   pass through the list), the ones that seemed likely enough to be
     45  #   used are listed in the man page and have explicit test scripts.
     46  #
     47  #   These had code changed in Script/Main.py to support them, but didn't
     48  #   seem likely to be used regularly, so had no test scripts added:
     49  #
     50  #       build --diskcheck=*
     51  #       build --implicit-cache=*
     52  #       build --implicit-deps-changed=*
     53  #       build --implicit-deps-unchanged=*
     54  #
     55  #   These look like they should "just work" with no changes to the
     56  #   existing code, but like those above, look unlikely to be used and
     57  #   therefore had no test scripts added:
     58  #
     59  #       build --random
     60  #
     61  #   These I'm not sure about.  They might be useful for individual
     62  #   "build" commands, and may even work, but they seem unlikely enough
     63  #   that we'll wait until they're requested before spending any time on
     64  #   writing test scripts for them, or investigating whether they work.
     65  #
     66  #       build -q [???  is there a useful analog to the exit status?]
     67  #       build --duplicate=
     68  #       build --profile=
     69  #       build --max-drift=
     70  #       build --warn=*
     71  #       build --Y
     72  #
     73  # - Most of the SCons command-line options that the "build" command
     74  #   supports should be settable as default options that apply to all
     75  #   subsequent "build" commands.  Maybe a "set {option}" command that
     76  #   maps to "SetOption('{option}')".
     77  #
     78  # - Need something in the 'help' command that prints the -h output.
     79  #
     80  # - A command to run the configure subsystem separately (must see how
     81  #   this interacts with the new automake model).
     82  #
     83  # - Command-line completion of target names; maybe even of SCons options?
     84  #   Completion is something that's supported by the Python cmd module,
     85  #   so this should be doable without too much trouble.
     86  #
     87
     88  import cmd
     89  import copy
     90  import os
     91  import re
     92  import shlex
     93  import string
     94  import sys
     95
     96  try:
     97      import readline
     98  except ImportError:
     99      pass
    100
    101  from SCons.Debug import Trace
    102
    103 -class SConsInteractiveCmd(cmd.Cmd):
    104      """\
    105      build [TARGETS]         Build the specified TARGETS and their dependencies.
    106                              'b' is a synonym.
    107      clean [TARGETS]         Clean (remove) the specified TARGETS and their
    108                              dependencies.  'c' is a synonym.
    109      exit                    Exit SCons interactive mode.
    110      help [COMMAND]          Prints help for the specified COMMAND.  'h' and
    111                              '?' are synonyms.
    112      shell [COMMANDLINE]     Execute COMMANDLINE in a subshell.  'sh' and '!'
    113                              are synonyms.
    114      version                 Prints SCons version information.
    115      """
    116
    117      synonyms = {
    118          'b'     : 'build',
    119          'c'     : 'clean',
    120          'h'     : 'help',
    121          'scons' : 'build',
    122          'sh'    : 'shell',
    123      }
    124
    125 -    def __init__(self, **kw):
    126          cmd.Cmd.__init__(self)
    127          for key, val in kw.items():
    128              setattr(self, key, val)
    129
    130          if sys.platform == 'win32':
    131              self.shell_variable = 'COMSPEC'
    132          else:
    133              self.shell_variable = 'SHELL'
    134
    135 -    def default(self, argv):
    136          print "*** Unknown command: %s" % argv[0]
    137
    138 -    def onecmd(self, line):
    139          line = string.strip(line)
    140          if not line:
    141              print self.lastcmd
    142              return self.emptyline()
    143          self.lastcmd = line
    144          if line[0] == '!':
    145              line = 'shell ' + line[1:]
    146          elif line[0] == '?':
    147              line = 'help ' + line[1:]
    148          argv = shlex.split(line)
    149          argv[0] = self.synonyms.get(argv[0], argv[0])
    150          if not argv[0]:
    151              return self.default(line)
    152          else:
    153              try:
    154                  func = getattr(self, 'do_' + argv[0])
    155              except AttributeError:
    156                  return self.default(argv)
    157              return func(argv)
    158
    159 -    def do_build(self, argv):
    160          """\
    161          build [TARGETS]         Build the specified TARGETS and their
    162                                  dependencies.  'b' is a synonym.
    163          """
    164          import SCons.SConsign
    165          import SCons.Script.Main
    166
    167          options = copy.deepcopy(self.options)
    168
    169          options, targets = self.parser.parse_args(argv[1:], values=options)
    170
    171          SCons.Script.COMMAND_LINE_TARGETS = targets
    172
    173          if targets:
    174              SCons.Script.BUILD_TARGETS = targets
    175          else:
    176              # If the user didn't specify any targets on the command line,
    177              # use the list of default targets.
    178              SCons.Script.BUILD_TARGETS = SCons.Script._build_plus_default
    179
    180          nodes = SCons.Script.Main._build_targets(self.fs,
    181                                                   options,
    182                                                   targets,
    183                                                   self.target_top)
    184
    185          if not nodes:
    186              return
    187
    188          # Clean up so that we can perform the next build correctly.
    189          #
    190          # We do this by walking over all the children of the targets,
    191          # and clearing their state.
    192          #
    193          # We currently have to re-scan each node to find their
    194          # children, because built nodes have already been partially
    195          # cleared and don't remember their children.  (In scons
    196          # 0.96.1 and earlier, this wasn't the case, and we didn't
    197          # have to re-scan the nodes.)
    198          #
    199          # Because we have to re-scan each node, we can't clear the
    200          # nodes as we walk over them, because we may end up rescanning
    201          # a cleared node as we scan a later node.  Therefore, only
    202          # store the list of nodes that need to be cleared as we walk
    203          # the tree, and clear them in a separate pass.
    204          #
    205          # XXX: Someone more familiar with the inner workings of scons
    206          # may be able to point out a more efficient way to do this.
    207
    208          SCons.Script.Main.progress_display("scons: Clearing cached node information ...")
    209
    210          seen_nodes = {}
    211
    212          def get_unseen_children(node, parent, seen_nodes=seen_nodes):
    213              def is_unseen(node, seen_nodes=seen_nodes):
    214                  return not seen_nodes.has_key(node)
    215              return filter(is_unseen, node.children(scan=1))
    216
    217          def add_to_seen_nodes(node, parent, seen_nodes=seen_nodes):
    218              seen_nodes[node] = 1
    219
    220              # If this file is in a VariantDir and has a
    221              # corresponding source file in the source tree, remember the
    222              # node in the source tree, too.  This is needed in
    223              # particular to clear cached implicit dependencies on the
    224              # source file, since the scanner will scan it if the
    225              # VariantDir was created with duplicate=0.
    226              try:
    227                  rfile_method = node.rfile
    228              except AttributeError:
    229                  return
    230              else:
    231                  rfile = rfile_method()
    232              if rfile != node:
    233                  seen_nodes[rfile] = 1
    234
    235          for node in nodes:
    236              walker = SCons.Node.Walker(node,
    237                                          kids_func=get_unseen_children,
    238                                          eval_func=add_to_seen_nodes)
    239              n = walker.next()
    240              while n:
    241                  n = walker.next()
    242
    243          for node in seen_nodes.keys():
    244              # Call node.clear() to clear most of the state
    245              node.clear()
    246              # node.clear() doesn't reset node.state, so call
    247              # node.set_state() to reset it manually
    248              node.set_state(SCons.Node.no_state)
    249              node.implicit = None
    250
    251          SCons.SConsign.Reset()
    252          SCons.Script.Main.progress_display("scons: done clearing node information.")
    253
    254 -    def do_clean(self, argv):
    255          """\
    256          clean [TARGETS]         Clean (remove) the specified TARGETS
    257                                  and their dependencies.  'c' is a synonym.
    258          """
    259          return self.do_build(['build', '--clean'] + argv[1:])
    260
    261 -    def do_EOF(self, argv):
    262          print
    263          self.do_exit(argv)
    264
    265 -    def _do_one_help(self, arg):
    266          try:
    267              # If help_<arg>() exists, then call it.
    268              func = getattr(self, 'help_' + arg)
    269          except AttributeError:
    270              try:
    271                  func = getattr(self, 'do_' + arg)
    272              except AttributeError:
    273                  doc = None
    274              else:
    275                  doc = self._doc_to_help(func)
    276              if doc:
    277                  sys.stdout.write(doc + '\n')
    278                  sys.stdout.flush()
    279          else:
    280              doc = self.strip_initial_spaces(func())
    281              if doc:
    282                  sys.stdout.write(doc + '\n')
    283                  sys.stdout.flush()
    284
    285 -    def _doc_to_help(self, obj):
    286          doc = obj.__doc__
    287          if doc is None:
    288              return ''
    289          return self._strip_initial_spaces(doc)
    290
    291 -    def _strip_initial_spaces(self, s):
    292          #lines = s.split('\n')
    293          lines = string.split(s, '\n')
    294          spaces = re.match(' *', lines[0]).group(0)
    295          #def strip_spaces(l):
    296          #    if l.startswith(spaces):
    297          #        l = l[len(spaces):]
    298          #    return l
    299          #return '\n'.join([ strip_spaces(l) for l in lines ])
    300          def strip_spaces(l, spaces=spaces):
    301              if l[:len(spaces)] == spaces:
    302                  l = l[len(spaces):]
    303              return l
    304          lines = map(strip_spaces, lines)
    305          return string.join(lines, '\n')
    306
    307 -    def do_exit(self, argv):
    308          """\
    309          exit                    Exit SCons interactive mode.
    310          """
    311          sys.exit(0)
    312
    313 -    def do_help(self, argv):
    314          """\
    315          help [COMMAND]          Prints help for the specified COMMAND.  'h'
    316                                  and '?' are synonyms.
    317          """
    318          if argv[1:]:
    319              for arg in argv[1:]:
    320                  if self._do_one_help(arg):
    321                      break
    322          else:
    323              # If bare 'help' is called, print this class's doc
    324              # string (if it has one).
    325              doc = self._doc_to_help(self.__class__)
    326              if doc:
    327                  sys.stdout.write(doc + '\n')
    328                  sys.stdout.flush()
    329
    330 -    def do_shell(self, argv):
    331          """\
    332          shell [COMMANDLINE]     Execute COMMANDLINE in a subshell.  'sh' and
    333                                  '!' are synonyms.
    334          """
    335          import subprocess
    336          argv = argv[1:]
    337          if not argv:
    338              argv = os.environ[self.shell_variable]
    339          try:
    340              p = subprocess.Popen(argv)
    341          except EnvironmentError, e:
    342              sys.stderr.write('scons: %s: %s\n' % (argv[0], e.strerror))
    343          else:
    344              p.wait()
    345
    346 -    def do_version(self, argv):
    347          """\
    348          version                 Prints SCons version information.
    349          """
    350          sys.stdout.write(self.parser.version + '\n')
    351
    352 -def interact(fs, parser, options, targets, target_top):
    353      c = SConsInteractiveCmd(prompt = 'scons>>> ',
    354                              fs = fs,
    355                              parser = parser,
    356                              options = options,
    357                              targets = targets,
    358                              target_top = target_top)
    359      c.cmdloop()
    360