summaryrefslogtreecommitdiffstats
path: root/src/engine/SCons/Script
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/SCons/Script')
-rw-r--r--src/engine/SCons/Script/Interactive.py359
-rw-r--r--src/engine/SCons/Script/Main.py104
-rw-r--r--src/engine/SCons/Script/SConsOptions.py23
3 files changed, 462 insertions, 24 deletions
diff --git a/src/engine/SCons/Script/Interactive.py b/src/engine/SCons/Script/Interactive.py
new file mode 100644
index 0000000..e38c400
--- /dev/null
+++ b/src/engine/SCons/Script/Interactive.py
@@ -0,0 +1,359 @@
+#
+# __COPYRIGHT__
+#
+# 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.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+__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 string
+import sys
+
+try:
+ import readline
+except ImportError:
+ pass
+
+from SCons.Debug import Trace
+
+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 = string.strip(line)
+ 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:]
+ 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.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
+
+ # 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 not seen_nodes.has_key(node)
+ return filter(is_unseen, node.children(scan=1))
+
+ def add_to_seen_nodes(node, parent, seen_nodes=seen_nodes):
+ seen_nodes[node] = 1
+
+ # If this file is in a BuildDir 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
+ # BuildDir 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.next()
+ while n:
+ n = walker.next()
+
+ for node in 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
+
+ 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')
+ lines = string.split(s, '\n')
+ spaces = re.match(' *', lines[0]).group(0)
+ #def strip_spaces(l):
+ # if l.startswith(spaces):
+ # l = l[len(spaces):]
+ # return l
+ #return '\n'.join([ strip_spaces(l) for l in lines ])
+ def strip_spaces(l, spaces=spaces):
+ if l[:len(spaces)] == spaces:
+ l = l[len(spaces):]
+ return l
+ lines = map(strip_spaces, lines)
+ return string.join(lines, '\n')
+
+ 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:
+ p = subprocess.Popen(argv)
+ except EnvironmentError, 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()
diff --git a/src/engine/SCons/Script/Main.py b/src/engine/SCons/Script/Main.py
index 97e0b19..bcbd0a1 100644
--- a/src/engine/SCons/Script/Main.py
+++ b/src/engine/SCons/Script/Main.py
@@ -68,6 +68,18 @@ import SCons.Taskmaster
import SCons.Util
import SCons.Warnings
+import SCons.Script.Interactive
+
+def fetch_win32_parallel_msg():
+ # A subsidiary function that exists solely to isolate this import
+ # so we don't have to pull it in on all platforms, and so that an
+ # in-line "import" statement in the _main() function below doesn't
+ # cause warnings about local names shadowing use of the 'SCons'
+ # globl in nest scopes and UnboundLocalErrors and the like in some
+ # versions (2.1) of Python.
+ import SCons.Platform.win32
+ SCons.Platform.win32.parallel_msg
+
#
class SConsPrintHelpException(Exception):
@@ -730,7 +742,6 @@ def version_string(label, module):
module.__buildsys__)
def _main(parser):
- import SCons
global exit_status
options = parser.values
@@ -750,7 +761,8 @@ def _main(parser):
SCons.Warnings.NoMetaclassSupportWarning,
SCons.Warnings.NoObjectCountWarning,
SCons.Warnings.NoParallelSupportWarning,
- SCons.Warnings.MisleadingKeywordsWarning, ]
+ SCons.Warnings.MisleadingKeywordsWarning,
+ SCons.Warnings.StackSizeWarning, ]
for warning in default_warnings:
SCons.Warnings.enableWarningClass(warning)
SCons.Warnings._warningOut = _scons_internal_warning
@@ -835,10 +847,10 @@ def _main(parser):
SCons.Node.implicit_cache = options.implicit_cache
SCons.Node.implicit_deps_changed = options.implicit_deps_changed
SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
+
if options.no_exec:
SCons.SConf.dryrun = 1
SCons.Action.execute_actions = None
- CleanTask.execute = CleanTask.show
if options.question:
SCons.SConf.dryrun = 1
if options.clean:
@@ -850,19 +862,6 @@ def _main(parser):
if options.no_progress or options.silent:
progress_display.set_mode(0)
- if options.silent:
- display.set_mode(0)
- if options.silent:
- SCons.Action.print_actions = None
-
- if options.cache_disable:
- SCons.CacheDir.CacheDir = SCons.Util.Null()
- if options.cache_debug:
- SCons.CacheDir.cache_debug = options.cache_debug
- if options.cache_force:
- SCons.CacheDir.cache_force = True
- if options.cache_show:
- SCons.CacheDir.cache_show = True
if options.site_dir:
_load_site_scons_dir(d, options.site_dir)
@@ -887,7 +886,18 @@ def _main(parser):
SCons.Script._Add_Targets(targets + parser.rargs)
SCons.Script._Add_Arguments(xmit_args)
- sys.stdout = SCons.Util.Unbuffered(sys.stdout)
+ # If stdout is not a tty, replace it with a wrapper object to call flush
+ # after every write.
+ #
+ # Tty devices automatically flush after every newline, so the replacement
+ # isn't necessary. Furthermore, if we replace sys.stdout, the readline
+ # module will no longer work. This affects the behavior during
+ # --interactive mode. --interactive should only be used when stdin and
+ # stdout refer to a tty.
+ if not sys.stdout.isatty():
+ sys.stdout = SCons.Util.Unbuffered(sys.stdout)
+ if not sys.stderr.isatty():
+ sys.stderr = SCons.Util.Unbuffered(sys.stderr)
memory_stats.append('before reading SConscript files:')
count_stats.append(('pre-', 'read'))
@@ -956,6 +966,47 @@ def _main(parser):
SCons.Node.implicit_cache = options.implicit_cache
SCons.Node.FS.set_duplicate(options.duplicate)
fs.set_max_drift(options.max_drift)
+ if not options.stack_size is None:
+ SCons.Job.stack_size = options.stack_size
+
+ platform = SCons.Platform.platform_module()
+
+ if options.interactive:
+ SCons.Script.Interactive.interact(fs, OptionsParser, options,
+ targets, target_top)
+
+ else:
+
+ # Build the targets
+ nodes = _build_targets(fs, options, targets, target_top)
+ if not nodes:
+ exit_status = 2
+
+def _build_targets(fs, options, targets, target_top):
+
+ progress_display.set_mode(not (options.no_progress or options.silent))
+ display.set_mode(not options.silent)
+ SCons.Action.print_actions = not options.silent
+ SCons.Action.execute_actions = not options.no_exec
+ SCons.SConf.dryrun = options.no_exec
+
+ if options.diskcheck:
+ SCons.Node.FS.set_diskcheck(options.diskcheck)
+
+ _set_debug_values(options)
+ SCons.Node.implicit_cache = options.implicit_cache
+ SCons.Node.implicit_deps_changed = options.implicit_deps_changed
+ SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
+
+ SCons.CacheDir.cache_enabled = not options.cache_disable
+ SCons.CacheDir.cache_debug = options.cache_debug
+ SCons.CacheDir.cache_force = options.cache_force
+ SCons.CacheDir.cache_show = options.cache_show
+
+ if options.no_exec:
+ CleanTask.execute = CleanTask.show
+ else:
+ CleanTask.execute = CleanTask.remove
lookup_top = None
if targets or SCons.Script.BUILD_TARGETS != SCons.Script._build_plus_default:
@@ -1003,7 +1054,7 @@ def _main(parser):
if not targets:
sys.stderr.write("scons: *** No targets specified and no Default() targets found. Stop.\n")
- sys.exit(2)
+ return None
def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
if isinstance(x, SCons.Node.Node):
@@ -1046,7 +1097,7 @@ def _main(parser):
opening_message = "Cleaning targets ..."
closing_message = "done cleaning targets."
if options.keep_going:
- closing_message = "done cleaning targets (errors occurred during clean)."
+ failure_message = "done cleaning targets (errors occurred during clean)."
else:
failure_message = "cleaning terminated because of errors."
except AttributeError:
@@ -1091,8 +1142,7 @@ def _main(parser):
msg = "parallel builds are unsupported by this version of Python;\n" + \
"\tignoring -j or num_jobs option.\n"
elif sys.platform == 'win32':
- import SCons.Platform.win32
- msg = SCons.Platform.win32.parallel_msg
+ msg = fetch_win32_parallel_msg()
if msg:
SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
@@ -1101,7 +1151,15 @@ def _main(parser):
try:
progress_display("scons: " + opening_message)
- jobs.run()
+ try:
+ jobs.run()
+ except KeyboardInterrupt:
+ # If we are in interactive mode, a KeyboardInterrupt
+ # interrupts only this current run. Return 'nodes' normally
+ # so that the outer loop can clean up the nodes and continue.
+ if options.interactive:
+ print "Build interrupted."
+ # Continue and return normally
finally:
jobs.cleanup()
if exit_status:
@@ -1114,6 +1172,8 @@ def _main(parser):
memory_stats.append('after building targets:')
count_stats.append(('post-', 'build'))
+ return nodes
+
def _exec_main(parser, values):
sconsflags = os.environ.get('SCONSFLAGS', '')
all_args = string.split(sconsflags) + sys.argv[1:]
diff --git a/src/engine/SCons/Script/SConsOptions.py b/src/engine/SCons/Script/SConsOptions.py
index 46ece27..8f7116d 100644
--- a/src/engine/SCons/Script/SConsOptions.py
+++ b/src/engine/SCons/Script/SConsOptions.py
@@ -122,6 +122,7 @@ class SConsValues(optparse.Values):
'no_exec',
'num_jobs',
'random',
+ 'stack_size',
]
def set_option(self, name, value):
@@ -163,6 +164,11 @@ class SConsValues(optparse.Values):
# Set this right away so it can affect the rest of the
# file/Node lookups while processing the SConscript files.
SCons.Node.FS.set_diskcheck(value)
+ elif name == 'stack_size':
+ try:
+ value = int(value)
+ except ValueError:
+ raise SCons.Errors.UserError, "An integer is required: %s"%repr(value)
self.__SConscript_settings__[name] = value
@@ -466,6 +472,7 @@ def Parser(version):
usage="usage: scons [OPTION] [TARGET] ...",)
op.preserve_unknown_options = True
+ op.version = version
# Add the options to the parser we just created.
#
@@ -667,6 +674,11 @@ def Parser(version):
action="callback", callback=opt_implicit_deps,
help="Ignore changes in implicit dependencies.")
+ op.add_option('--interact', '--interactive',
+ dest='interactive', default=False,
+ action="store_true",
+ help="Run in interactive mode.")
+
op.add_option('-j', '--jobs',
nargs=1, type="int",
dest="num_jobs", default=1,
@@ -730,6 +742,13 @@ def Parser(version):
help="Use DIR instead of the usual site_scons dir.",
metavar="DIR")
+ op.add_option('--stack-size',
+ nargs=1, type="int",
+ dest='stack_size',
+ action="store",
+ help="Set the stack size of the threads used to run jobs to N kilobytes.",
+ metavar="N")
+
op.add_option('--taskmastertrace',
nargs=1,
dest="taskmastertrace_file", default=None,
@@ -777,8 +796,8 @@ def Parser(version):
help="Search up directory tree for SConstruct, "
"build Default() targets from local SConscript.")
- def opt_version(option, opt, value, parser, version=version):
- sys.stdout.write(version + '\n')
+ def opt_version(option, opt, value, parser):
+ sys.stdout.write(parser.version + '\n')
sys.exit(0)
op.add_option("-v", "--version",
action="callback", callback=opt_version,