| # |
| # Copyright (c) 2001 - 2019 The SCons Foundation |
| # |
| # Permission is hereby granted, free of charge, to any person obtaining |
| # a copy of this software and associated documentation files (the |
| # "Software"), to deal in the Software without restriction, including |
| # without limitation the rights to use, copy, modify, merge, publish, |
| # distribute, sublicense, and/or sell copies of the Software, and to |
| # permit persons to whom the Software is furnished to do so, subject to |
| # the following conditions: |
| # |
| # The above copyright notice and this permission notice shall be included |
| # in all copies or substantial portions of the Software. |
| # |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY |
| # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE |
| # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
| # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
| # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
| # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| from __future__ import print_function |
| |
| __revision__ = "src/engine/SCons/Script/Interactive.py bee7caf9defd6e108fc2998a2520ddb36a967691 2019-12-17 02:07:09 bdeegan" |
| |
| __doc__ = """ |
| SCons interactive mode |
| """ |
| |
| # TODO: |
| # |
| # This has the potential to grow into something with a really big life |
| # of its own, which might or might not be a good thing. Nevertheless, |
| # here are some enhancements that will probably be requested some day |
| # and are worth keeping in mind (assuming this takes off): |
| # |
| # - A command to re-read / re-load the SConscript files. This may |
| # involve allowing people to specify command-line options (e.g. -f, |
| # -I, --no-site-dir) that affect how the SConscript files are read. |
| # |
| # - Additional command-line options on the "build" command. |
| # |
| # Of the supported options that seemed to make sense (after a quick |
| # pass through the list), the ones that seemed likely enough to be |
| # used are listed in the man page and have explicit test scripts. |
| # |
| # These had code changed in Script/Main.py to support them, but didn't |
| # seem likely to be used regularly, so had no test scripts added: |
| # |
| # build --diskcheck=* |
| # build --implicit-cache=* |
| # build --implicit-deps-changed=* |
| # build --implicit-deps-unchanged=* |
| # |
| # These look like they should "just work" with no changes to the |
| # existing code, but like those above, look unlikely to be used and |
| # therefore had no test scripts added: |
| # |
| # build --random |
| # |
| # These I'm not sure about. They might be useful for individual |
| # "build" commands, and may even work, but they seem unlikely enough |
| # that we'll wait until they're requested before spending any time on |
| # writing test scripts for them, or investigating whether they work. |
| # |
| # build -q [??? is there a useful analog to the exit status?] |
| # build --duplicate= |
| # build --profile= |
| # build --max-drift= |
| # build --warn=* |
| # build --Y |
| # |
| # - Most of the SCons command-line options that the "build" command |
| # supports should be settable as default options that apply to all |
| # subsequent "build" commands. Maybe a "set {option}" command that |
| # maps to "SetOption('{option}')". |
| # |
| # - Need something in the 'help' command that prints the -h output. |
| # |
| # - A command to run the configure subsystem separately (must see how |
| # this interacts with the new automake model). |
| # |
| # - Command-line completion of target names; maybe even of SCons options? |
| # Completion is something that's supported by the Python cmd module, |
| # so this should be doable without too much trouble. |
| # |
| |
| import cmd |
| import copy |
| import os |
| import re |
| import shlex |
| import sys |
| |
| try: |
| import readline |
| except ImportError: |
| pass |
| |
| class SConsInteractiveCmd(cmd.Cmd): |
| """\ |
| |
| build [TARGETS] Build the specified TARGETS and their dependencies. 'b' is a synonym. |
| clean [TARGETS] Clean (remove) the specified TARGETS and their dependencies. 'c' is a synonym. |
| exit Exit SCons interactive mode. |
| help [COMMAND] Prints help for the specified COMMAND. 'h' and '?' are synonyms. |
| shell [COMMANDLINE] Execute COMMANDLINE in a subshell. 'sh' and '!' are synonyms. |
| version Prints SCons version information. |
| """ |
| |
| synonyms = { |
| 'b' : 'build', |
| 'c' : 'clean', |
| 'h' : 'help', |
| 'scons' : 'build', |
| 'sh' : 'shell', |
| } |
| |
| def __init__(self, **kw): |
| cmd.Cmd.__init__(self) |
| for key, val in kw.items(): |
| setattr(self, key, val) |
| |
| if sys.platform == 'win32': |
| self.shell_variable = 'COMSPEC' |
| else: |
| self.shell_variable = 'SHELL' |
| |
| def default(self, argv): |
| print("*** Unknown command: %s" % argv[0]) |
| |
| def onecmd(self, line): |
| line = line.strip() |
| if not line: |
| print(self.lastcmd) |
| return self.emptyline() |
| self.lastcmd = line |
| if line[0] == '!': |
| line = 'shell ' + line[1:] |
| elif line[0] == '?': |
| line = 'help ' + line[1:] |
| if os.sep == '\\': |
| line = line.replace('\\', '\\\\') |
| argv = shlex.split(line) |
| argv[0] = self.synonyms.get(argv[0], argv[0]) |
| if not argv[0]: |
| return self.default(line) |
| else: |
| try: |
| func = getattr(self, 'do_' + argv[0]) |
| except AttributeError: |
| return self.default(argv) |
| return func(argv) |
| |
| def do_build(self, argv): |
| """\ |
| build [TARGETS] Build the specified TARGETS and their |
| dependencies. 'b' is a synonym. |
| """ |
| import SCons.Node |
| import SCons.SConsign |
| import SCons.Script.Main |
| |
| options = copy.deepcopy(self.options) |
| |
| options, targets = self.parser.parse_args(argv[1:], values=options) |
| |
| SCons.Script.COMMAND_LINE_TARGETS = targets |
| |
| if targets: |
| SCons.Script.BUILD_TARGETS = targets |
| else: |
| # If the user didn't specify any targets on the command line, |
| # use the list of default targets. |
| SCons.Script.BUILD_TARGETS = SCons.Script._build_plus_default |
| |
| nodes = SCons.Script.Main._build_targets(self.fs, |
| options, |
| targets, |
| self.target_top) |
| |
| if not nodes: |
| return |
| |
| # Call each of the Node's alter_targets() methods, which may |
| # provide additional targets that ended up as part of the build |
| # (the canonical example being a VariantDir() when we're building |
| # from a source directory) and which we therefore need their |
| # state cleared, too. |
| x = [] |
| for n in nodes: |
| x.extend(n.alter_targets()[0]) |
| nodes.extend(x) |
| |
| # Clean up so that we can perform the next build correctly. |
| # |
| # We do this by walking over all the children of the targets, |
| # and clearing their state. |
| # |
| # We currently have to re-scan each node to find their |
| # children, because built nodes have already been partially |
| # cleared and don't remember their children. (In scons |
| # 0.96.1 and earlier, this wasn't the case, and we didn't |
| # have to re-scan the nodes.) |
| # |
| # Because we have to re-scan each node, we can't clear the |
| # nodes as we walk over them, because we may end up rescanning |
| # a cleared node as we scan a later node. Therefore, only |
| # store the list of nodes that need to be cleared as we walk |
| # the tree, and clear them in a separate pass. |
| # |
| # XXX: Someone more familiar with the inner workings of scons |
| # may be able to point out a more efficient way to do this. |
| |
| SCons.Script.Main.progress_display("scons: Clearing cached node information ...") |
| |
| seen_nodes = {} |
| |
| def get_unseen_children(node, parent, seen_nodes=seen_nodes): |
| def is_unseen(node, seen_nodes=seen_nodes): |
| return node not in seen_nodes |
| return [child for child in node.children(scan=1) if is_unseen(child)] |
| |
| def add_to_seen_nodes(node, parent, seen_nodes=seen_nodes): |
| seen_nodes[node] = 1 |
| |
| # If this file is in a VariantDir and has a |
| # corresponding source file in the source tree, remember the |
| # node in the source tree, too. This is needed in |
| # particular to clear cached implicit dependencies on the |
| # source file, since the scanner will scan it if the |
| # VariantDir was created with duplicate=0. |
| try: |
| rfile_method = node.rfile |
| except AttributeError: |
| return |
| else: |
| rfile = rfile_method() |
| if rfile != node: |
| seen_nodes[rfile] = 1 |
| |
| for node in nodes: |
| walker = SCons.Node.Walker(node, |
| kids_func=get_unseen_children, |
| eval_func=add_to_seen_nodes) |
| n = walker.get_next() |
| while n: |
| n = walker.get_next() |
| |
| for node in list(seen_nodes.keys()): |
| # Call node.clear() to clear most of the state |
| node.clear() |
| # node.clear() doesn't reset node.state, so call |
| # node.set_state() to reset it manually |
| node.set_state(SCons.Node.no_state) |
| node.implicit = None |
| |
| # Debug: Uncomment to verify that all Taskmaster reference |
| # counts have been reset to zero. |
| #if node.ref_count != 0: |
| # from SCons.Debug import Trace |
| # Trace('node %s, ref_count %s !!!\n' % (node, node.ref_count)) |
| |
| SCons.SConsign.Reset() |
| SCons.Script.Main.progress_display("scons: done clearing node information.") |
| |
| def do_clean(self, argv): |
| """\ |
| clean [TARGETS] Clean (remove) the specified TARGETS |
| and their dependencies. 'c' is a synonym. |
| """ |
| return self.do_build(['build', '--clean'] + argv[1:]) |
| |
| def do_EOF(self, argv): |
| print() |
| self.do_exit(argv) |
| |
| def _do_one_help(self, arg): |
| try: |
| # If help_<arg>() exists, then call it. |
| func = getattr(self, 'help_' + arg) |
| except AttributeError: |
| try: |
| func = getattr(self, 'do_' + arg) |
| except AttributeError: |
| doc = None |
| else: |
| doc = self._doc_to_help(func) |
| if doc: |
| sys.stdout.write(doc + '\n') |
| sys.stdout.flush() |
| else: |
| doc = self.strip_initial_spaces(func()) |
| if doc: |
| sys.stdout.write(doc + '\n') |
| sys.stdout.flush() |
| |
| def _doc_to_help(self, obj): |
| doc = obj.__doc__ |
| if doc is None: |
| return '' |
| return self._strip_initial_spaces(doc) |
| |
| def _strip_initial_spaces(self, s): |
| lines = s.split('\n') |
| spaces = re.match(' *', lines[0]).group(0) |
| def strip_spaces(l, spaces=spaces): |
| if l[:len(spaces)] == spaces: |
| l = l[len(spaces):] |
| return l |
| lines = list(map(strip_spaces, lines)) |
| return '\n'.join(lines) |
| |
| def do_exit(self, argv): |
| """\ |
| exit Exit SCons interactive mode. |
| """ |
| sys.exit(0) |
| |
| def do_help(self, argv): |
| """\ |
| help [COMMAND] Prints help for the specified COMMAND. 'h' |
| and '?' are synonyms. |
| """ |
| if argv[1:]: |
| for arg in argv[1:]: |
| if self._do_one_help(arg): |
| break |
| else: |
| # If bare 'help' is called, print this class's doc |
| # string (if it has one). |
| doc = self._doc_to_help(self.__class__) |
| if doc: |
| sys.stdout.write(doc + '\n') |
| sys.stdout.flush() |
| |
| def do_shell(self, argv): |
| """\ |
| shell [COMMANDLINE] Execute COMMANDLINE in a subshell. 'sh' and |
| '!' are synonyms. |
| """ |
| import subprocess |
| argv = argv[1:] |
| if not argv: |
| argv = os.environ[self.shell_variable] |
| try: |
| # Per "[Python-Dev] subprocess insufficiently platform-independent?" |
| # http://mail.python.org/pipermail/python-dev/2008-August/081979.html "+ |
| # Doing the right thing with an argument list currently |
| # requires different shell= values on Windows and Linux. |
| p = subprocess.Popen(argv, shell=(sys.platform=='win32')) |
| except EnvironmentError as e: |
| sys.stderr.write('scons: %s: %s\n' % (argv[0], e.strerror)) |
| else: |
| p.wait() |
| |
| def do_version(self, argv): |
| """\ |
| version Prints SCons version information. |
| """ |
| sys.stdout.write(self.parser.version + '\n') |
| |
| def interact(fs, parser, options, targets, target_top): |
| c = SConsInteractiveCmd(prompt = 'scons>>> ', |
| fs = fs, |
| parser = parser, |
| options = options, |
| targets = targets, |
| target_top = target_top) |
| c.cmdloop() |
| |
| # Local Variables: |
| # tab-width:4 |
| # indent-tabs-mode:nil |
| # End: |
| # vim: set expandtab tabstop=4 shiftwidth=4: |