OLD | NEW |
(Empty) | |
| 1 # |
| 2 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 The S
Cons 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 __revision__ = "src/engine/SCons/Script/Interactive.py 5134 2010/08/16 23:02:40
bdeegan" |
| 24 |
| 25 __doc__ = """ |
| 26 SCons interactive mode |
| 27 """ |
| 28 |
| 29 # TODO: |
| 30 # |
| 31 # This has the potential to grow into something with a really big life |
| 32 # of its own, which might or might not be a good thing. Nevertheless, |
| 33 # here are some enhancements that will probably be requested some day |
| 34 # and are worth keeping in mind (assuming this takes off): |
| 35 # |
| 36 # - A command to re-read / re-load the SConscript files. This may |
| 37 # involve allowing people to specify command-line options (e.g. -f, |
| 38 # -I, --no-site-dir) that affect how the SConscript files are read. |
| 39 # |
| 40 # - Additional command-line options on the "build" command. |
| 41 # |
| 42 # Of the supported options that seemed to make sense (after a quick |
| 43 # pass through the list), the ones that seemed likely enough to be |
| 44 # used are listed in the man page and have explicit test scripts. |
| 45 # |
| 46 # These had code changed in Script/Main.py to support them, but didn't |
| 47 # seem likely to be used regularly, so had no test scripts added: |
| 48 # |
| 49 # build --diskcheck=* |
| 50 # build --implicit-cache=* |
| 51 # build --implicit-deps-changed=* |
| 52 # build --implicit-deps-unchanged=* |
| 53 # |
| 54 # These look like they should "just work" with no changes to the |
| 55 # existing code, but like those above, look unlikely to be used and |
| 56 # therefore had no test scripts added: |
| 57 # |
| 58 # build --random |
| 59 # |
| 60 # These I'm not sure about. They might be useful for individual |
| 61 # "build" commands, and may even work, but they seem unlikely enough |
| 62 # that we'll wait until they're requested before spending any time on |
| 63 # writing test scripts for them, or investigating whether they work. |
| 64 # |
| 65 # build -q [??? is there a useful analog to the exit status?] |
| 66 # build --duplicate= |
| 67 # build --profile= |
| 68 # build --max-drift= |
| 69 # build --warn=* |
| 70 # build --Y |
| 71 # |
| 72 # - Most of the SCons command-line options that the "build" command |
| 73 # supports should be settable as default options that apply to all |
| 74 # subsequent "build" commands. Maybe a "set {option}" command that |
| 75 # maps to "SetOption('{option}')". |
| 76 # |
| 77 # - Need something in the 'help' command that prints the -h output. |
| 78 # |
| 79 # - A command to run the configure subsystem separately (must see how |
| 80 # this interacts with the new automake model). |
| 81 # |
| 82 # - Command-line completion of target names; maybe even of SCons options? |
| 83 # Completion is something that's supported by the Python cmd module, |
| 84 # so this should be doable without too much trouble. |
| 85 # |
| 86 |
| 87 import cmd |
| 88 import copy |
| 89 import os |
| 90 import re |
| 91 import shlex |
| 92 import sys |
| 93 |
| 94 try: |
| 95 import readline |
| 96 except ImportError: |
| 97 pass |
| 98 |
| 99 class SConsInteractiveCmd(cmd.Cmd): |
| 100 """\ |
| 101 build [TARGETS] Build the specified TARGETS and their dependencies. |
| 102 'b' is a synonym. |
| 103 clean [TARGETS] Clean (remove) the specified TARGETS and their |
| 104 dependencies. 'c' is a synonym. |
| 105 exit Exit SCons interactive mode. |
| 106 help [COMMAND] Prints help for the specified COMMAND. 'h' and |
| 107 '?' are synonyms. |
| 108 shell [COMMANDLINE] Execute COMMANDLINE in a subshell. 'sh' and '!' |
| 109 are synonyms. |
| 110 version Prints SCons version information. |
| 111 """ |
| 112 |
| 113 synonyms = { |
| 114 'b' : 'build', |
| 115 'c' : 'clean', |
| 116 'h' : 'help', |
| 117 'scons' : 'build', |
| 118 'sh' : 'shell', |
| 119 } |
| 120 |
| 121 def __init__(self, **kw): |
| 122 cmd.Cmd.__init__(self) |
| 123 for key, val in kw.items(): |
| 124 setattr(self, key, val) |
| 125 |
| 126 if sys.platform == 'win32': |
| 127 self.shell_variable = 'COMSPEC' |
| 128 else: |
| 129 self.shell_variable = 'SHELL' |
| 130 |
| 131 def default(self, argv): |
| 132 print "*** Unknown command: %s" % argv[0] |
| 133 |
| 134 def onecmd(self, line): |
| 135 line = line.strip() |
| 136 if not line: |
| 137 print self.lastcmd |
| 138 return self.emptyline() |
| 139 self.lastcmd = line |
| 140 if line[0] == '!': |
| 141 line = 'shell ' + line[1:] |
| 142 elif line[0] == '?': |
| 143 line = 'help ' + line[1:] |
| 144 if os.sep == '\\': |
| 145 line = line.replace('\\', '\\\\') |
| 146 argv = shlex.split(line) |
| 147 argv[0] = self.synonyms.get(argv[0], argv[0]) |
| 148 if not argv[0]: |
| 149 return self.default(line) |
| 150 else: |
| 151 try: |
| 152 func = getattr(self, 'do_' + argv[0]) |
| 153 except AttributeError: |
| 154 return self.default(argv) |
| 155 return func(argv) |
| 156 |
| 157 def do_build(self, argv): |
| 158 """\ |
| 159 build [TARGETS] Build the specified TARGETS and their |
| 160 dependencies. 'b' is a synonym. |
| 161 """ |
| 162 import SCons.Node |
| 163 import SCons.SConsign |
| 164 import SCons.Script.Main |
| 165 |
| 166 options = copy.deepcopy(self.options) |
| 167 |
| 168 options, targets = self.parser.parse_args(argv[1:], values=options) |
| 169 |
| 170 SCons.Script.COMMAND_LINE_TARGETS = targets |
| 171 |
| 172 if targets: |
| 173 SCons.Script.BUILD_TARGETS = targets |
| 174 else: |
| 175 # If the user didn't specify any targets on the command line, |
| 176 # use the list of default targets. |
| 177 SCons.Script.BUILD_TARGETS = SCons.Script._build_plus_default |
| 178 |
| 179 nodes = SCons.Script.Main._build_targets(self.fs, |
| 180 options, |
| 181 targets, |
| 182 self.target_top) |
| 183 |
| 184 if not nodes: |
| 185 return |
| 186 |
| 187 # Call each of the Node's alter_targets() methods, which may |
| 188 # provide additional targets that ended up as part of the build |
| 189 # (the canonical example being a VariantDir() when we're building |
| 190 # from a source directory) and which we therefore need their |
| 191 # state cleared, too. |
| 192 x = [] |
| 193 for n in nodes: |
| 194 x.extend(n.alter_targets()[0]) |
| 195 nodes.extend(x) |
| 196 |
| 197 # Clean up so that we can perform the next build correctly. |
| 198 # |
| 199 # We do this by walking over all the children of the targets, |
| 200 # and clearing their state. |
| 201 # |
| 202 # We currently have to re-scan each node to find their |
| 203 # children, because built nodes have already been partially |
| 204 # cleared and don't remember their children. (In scons |
| 205 # 0.96.1 and earlier, this wasn't the case, and we didn't |
| 206 # have to re-scan the nodes.) |
| 207 # |
| 208 # Because we have to re-scan each node, we can't clear the |
| 209 # nodes as we walk over them, because we may end up rescanning |
| 210 # a cleared node as we scan a later node. Therefore, only |
| 211 # store the list of nodes that need to be cleared as we walk |
| 212 # the tree, and clear them in a separate pass. |
| 213 # |
| 214 # XXX: Someone more familiar with the inner workings of scons |
| 215 # may be able to point out a more efficient way to do this. |
| 216 |
| 217 SCons.Script.Main.progress_display("scons: Clearing cached node informat
ion ...") |
| 218 |
| 219 seen_nodes = {} |
| 220 |
| 221 def get_unseen_children(node, parent, seen_nodes=seen_nodes): |
| 222 def is_unseen(node, seen_nodes=seen_nodes): |
| 223 return node not in seen_nodes |
| 224 return list(filter(is_unseen, node.children(scan=1))) |
| 225 |
| 226 def add_to_seen_nodes(node, parent, seen_nodes=seen_nodes): |
| 227 seen_nodes[node] = 1 |
| 228 |
| 229 # If this file is in a VariantDir and has a |
| 230 # corresponding source file in the source tree, remember the |
| 231 # node in the source tree, too. This is needed in |
| 232 # particular to clear cached implicit dependencies on the |
| 233 # source file, since the scanner will scan it if the |
| 234 # VariantDir was created with duplicate=0. |
| 235 try: |
| 236 rfile_method = node.rfile |
| 237 except AttributeError: |
| 238 return |
| 239 else: |
| 240 rfile = rfile_method() |
| 241 if rfile != node: |
| 242 seen_nodes[rfile] = 1 |
| 243 |
| 244 for node in nodes: |
| 245 walker = SCons.Node.Walker(node, |
| 246 kids_func=get_unseen_children, |
| 247 eval_func=add_to_seen_nodes) |
| 248 n = walker.get_next() |
| 249 while n: |
| 250 n = walker.get_next() |
| 251 |
| 252 for node in seen_nodes.keys(): |
| 253 # Call node.clear() to clear most of the state |
| 254 node.clear() |
| 255 # node.clear() doesn't reset node.state, so call |
| 256 # node.set_state() to reset it manually |
| 257 node.set_state(SCons.Node.no_state) |
| 258 node.implicit = None |
| 259 |
| 260 # Debug: Uncomment to verify that all Taskmaster reference |
| 261 # counts have been reset to zero. |
| 262 #if node.ref_count != 0: |
| 263 # from SCons.Debug import Trace |
| 264 # Trace('node %s, ref_count %s !!!\n' % (node, node.ref_count)) |
| 265 |
| 266 SCons.SConsign.Reset() |
| 267 SCons.Script.Main.progress_display("scons: done clearing node informatio
n.") |
| 268 |
| 269 def do_clean(self, argv): |
| 270 """\ |
| 271 clean [TARGETS] Clean (remove) the specified TARGETS |
| 272 and their dependencies. 'c' is a synonym. |
| 273 """ |
| 274 return self.do_build(['build', '--clean'] + argv[1:]) |
| 275 |
| 276 def do_EOF(self, argv): |
| 277 print |
| 278 self.do_exit(argv) |
| 279 |
| 280 def _do_one_help(self, arg): |
| 281 try: |
| 282 # If help_<arg>() exists, then call it. |
| 283 func = getattr(self, 'help_' + arg) |
| 284 except AttributeError: |
| 285 try: |
| 286 func = getattr(self, 'do_' + arg) |
| 287 except AttributeError: |
| 288 doc = None |
| 289 else: |
| 290 doc = self._doc_to_help(func) |
| 291 if doc: |
| 292 sys.stdout.write(doc + '\n') |
| 293 sys.stdout.flush() |
| 294 else: |
| 295 doc = self.strip_initial_spaces(func()) |
| 296 if doc: |
| 297 sys.stdout.write(doc + '\n') |
| 298 sys.stdout.flush() |
| 299 |
| 300 def _doc_to_help(self, obj): |
| 301 doc = obj.__doc__ |
| 302 if doc is None: |
| 303 return '' |
| 304 return self._strip_initial_spaces(doc) |
| 305 |
| 306 def _strip_initial_spaces(self, s): |
| 307 #lines = s.split('\n') |
| 308 lines = s.split('\n') |
| 309 spaces = re.match(' *', lines[0]).group(0) |
| 310 #def strip_spaces(l): |
| 311 # if l.startswith(spaces): |
| 312 # l = l[len(spaces):] |
| 313 # return l |
| 314 #return '\n'.join([ strip_spaces(l) for l in lines ]) |
| 315 def strip_spaces(l, spaces=spaces): |
| 316 if l[:len(spaces)] == spaces: |
| 317 l = l[len(spaces):] |
| 318 return l |
| 319 lines = list(map(strip_spaces, lines)) |
| 320 return '\n'.join(lines) |
| 321 |
| 322 def do_exit(self, argv): |
| 323 """\ |
| 324 exit Exit SCons interactive mode. |
| 325 """ |
| 326 sys.exit(0) |
| 327 |
| 328 def do_help(self, argv): |
| 329 """\ |
| 330 help [COMMAND] Prints help for the specified COMMAND. 'h' |
| 331 and '?' are synonyms. |
| 332 """ |
| 333 if argv[1:]: |
| 334 for arg in argv[1:]: |
| 335 if self._do_one_help(arg): |
| 336 break |
| 337 else: |
| 338 # If bare 'help' is called, print this class's doc |
| 339 # string (if it has one). |
| 340 doc = self._doc_to_help(self.__class__) |
| 341 if doc: |
| 342 sys.stdout.write(doc + '\n') |
| 343 sys.stdout.flush() |
| 344 |
| 345 def do_shell(self, argv): |
| 346 """\ |
| 347 shell [COMMANDLINE] Execute COMMANDLINE in a subshell. 'sh' and |
| 348 '!' are synonyms. |
| 349 """ |
| 350 import subprocess |
| 351 argv = argv[1:] |
| 352 if not argv: |
| 353 argv = os.environ[self.shell_variable] |
| 354 try: |
| 355 # Per "[Python-Dev] subprocess insufficiently platform-independent?" |
| 356 # http://mail.python.org/pipermail/python-dev/2008-August/081979.htm
l "+ |
| 357 # Doing the right thing with an argument list currently |
| 358 # requires different shell= values on Windows and Linux. |
| 359 p = subprocess.Popen(argv, shell=(sys.platform=='win32')) |
| 360 except EnvironmentError, e: |
| 361 sys.stderr.write('scons: %s: %s\n' % (argv[0], e.strerror)) |
| 362 else: |
| 363 p.wait() |
| 364 |
| 365 def do_version(self, argv): |
| 366 """\ |
| 367 version Prints SCons version information. |
| 368 """ |
| 369 sys.stdout.write(self.parser.version + '\n') |
| 370 |
| 371 def interact(fs, parser, options, targets, target_top): |
| 372 c = SConsInteractiveCmd(prompt = 'scons>>> ', |
| 373 fs = fs, |
| 374 parser = parser, |
| 375 options = options, |
| 376 targets = targets, |
| 377 target_top = target_top) |
| 378 c.cmdloop() |
| 379 |
| 380 # Local Variables: |
| 381 # tab-width:4 |
| 382 # indent-tabs-mode:nil |
| 383 # End: |
| 384 # vim: set expandtab tabstop=4 shiftwidth=4: |
OLD | NEW |