1# 2# Copyright (c) 2001 - 2016 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__revision__ = "src/engine/SCons/Script/Interactive.py rel_2.5.0:3543:937e55cd78f7 2016/04/09 11:29:54 bdbaddog" 24 25__doc__ = """ 26SCons 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 87import cmd 88import copy 89import os 90import re 91import shlex 92import sys 93 94try: 95 import readline 96except ImportError: 97 pass 98 99class 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 information ...") 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 information.") 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 spaces = re.match(' *', lines[0]).group(0) 309 def strip_spaces(l, spaces=spaces): 310 if l[:len(spaces)] == spaces: 311 l = l[len(spaces):] 312 return l 313 lines = list(map(strip_spaces, lines)) 314 return '\n'.join(lines) 315 316 def do_exit(self, argv): 317 """\ 318 exit Exit SCons interactive mode. 319 """ 320 sys.exit(0) 321 322 def do_help(self, argv): 323 """\ 324 help [COMMAND] Prints help for the specified COMMAND. 'h' 325 and '?' are synonyms. 326 """ 327 if argv[1:]: 328 for arg in argv[1:]: 329 if self._do_one_help(arg): 330 break 331 else: 332 # If bare 'help' is called, print this class's doc 333 # string (if it has one). 334 doc = self._doc_to_help(self.__class__) 335 if doc: 336 sys.stdout.write(doc + '\n') 337 sys.stdout.flush() 338 339 def do_shell(self, argv): 340 """\ 341 shell [COMMANDLINE] Execute COMMANDLINE in a subshell. 'sh' and 342 '!' are synonyms. 343 """ 344 import subprocess 345 argv = argv[1:] 346 if not argv: 347 argv = os.environ[self.shell_variable] 348 try: 349 # Per "[Python-Dev] subprocess insufficiently platform-independent?" 350 # http://mail.python.org/pipermail/python-dev/2008-August/081979.html "+ 351 # Doing the right thing with an argument list currently 352 # requires different shell= values on Windows and Linux. 353 p = subprocess.Popen(argv, shell=(sys.platform=='win32')) 354 except EnvironmentError, e: 355 sys.stderr.write('scons: %s: %s\n' % (argv[0], e.strerror)) 356 else: 357 p.wait() 358 359 def do_version(self, argv): 360 """\ 361 version Prints SCons version information. 362 """ 363 sys.stdout.write(self.parser.version + '\n') 364 365def interact(fs, parser, options, targets, target_top): 366 c = SConsInteractiveCmd(prompt = 'scons>>> ', 367 fs = fs, 368 parser = parser, 369 options = options, 370 targets = targets, 371 target_top = target_top) 372 c.cmdloop() 373 374# Local Variables: 375# tab-width:4 376# indent-tabs-mode:nil 377# End: 378# vim: set expandtab tabstop=4 shiftwidth=4: 379