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