summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--QMTest/TestSCons.py21
-rw-r--r--QMTest/TestSCons_time.py13
-rw-r--r--doc/man/scons.190
-rw-r--r--doc/user/libraries.in2
-rw-r--r--doc/user/libraries.sgml2
-rw-r--r--src/CHANGES.txt11
-rw-r--r--src/engine/MANIFEST.in7
-rw-r--r--src/engine/SCons/Defaults.py119
-rw-r--r--src/engine/SCons/Environment.py2
-rw-r--r--src/engine/SCons/Job.py49
-rw-r--r--src/engine/SCons/JobTests.py8
-rw-r--r--src/engine/SCons/Node/FS.py100
-rw-r--r--src/engine/SCons/Node/FSTests.py2
-rw-r--r--src/engine/SCons/Optik/__init__.py32
-rw-r--r--src/engine/SCons/Optik/errors.py55
-rw-r--r--src/engine/SCons/Optik/option.py388
-rw-r--r--src/engine/SCons/Optik/option_parser.py730
-rw-r--r--src/engine/SCons/SConfTests.py2
-rw-r--r--src/engine/SCons/Scanner/ScannerTests.py6
-rw-r--r--src/engine/SCons/Script/Main.py688
-rw-r--r--src/engine/SCons/Script/MainTests.py30
-rw-r--r--src/engine/SCons/Script/SConsOptions.py819
-rw-r--r--src/engine/SCons/Script/SConscript.py4
-rw-r--r--src/engine/SCons/Script/__init__.py23
-rw-r--r--src/engine/SCons/Warnings.py3
-rw-r--r--src/engine/SCons/compat/__init__.py14
-rw-r--r--src/engine/SCons/compat/_scons_optparse.py1719
-rw-r--r--src/engine/SCons/compat/_scons_textwrap.py373
-rw-r--r--src/engine/SCons/compat/builtins.py32
-rw-r--r--src/script/scons-time.py62
-rw-r--r--test/AS/AS.py317
-rw-r--r--test/AS/as-live.py184
-rw-r--r--test/AS/ml.py124
-rw-r--r--test/AS/nasm.py149
-rw-r--r--test/AddOption/basic.py70
-rw-r--r--test/AddOption/help.py82
-rw-r--r--test/Configure/ConfigureDryRunError.py16
-rw-r--r--test/Configure/option--config.py9
-rw-r--r--test/Java/RMIC.py2
-rw-r--r--test/LIBPATH.py4
-rw-r--r--test/NodeOps.py4
-rw-r--r--test/Options/BoolOption.py3
-rw-r--r--test/Options/EnumOption.py9
-rw-r--r--test/Options/ListOption.py15
-rw-r--r--test/Options/PackageOption.py3
-rw-r--r--test/Options/PathOption.py21
-rw-r--r--test/Perforce/Perforce.py2
-rw-r--r--test/QT/Tool.py8
-rw-r--r--test/QT/warnings.py6
-rw-r--r--test/RCS/diskcheck.py8
-rw-r--r--test/Repository/RMIC.py2
-rw-r--r--test/SCCS/diskcheck.py11
-rw-r--r--test/SWIG/live.py23
-rw-r--r--test/SWIG/remove-modules.py9
-rw-r--r--test/Scanner/exception.py2
-rw-r--r--test/Script-import.py45
-rw-r--r--test/TEX/auxiliaries.py16
-rw-r--r--test/TEX/bibtex-latex-rerun.py14
-rw-r--r--test/bad-variables.py7
-rw-r--r--test/compat/all.py59
-rw-r--r--test/compat/any.py57
-rw-r--r--test/exceptions.py6
-rw-r--r--test/explain.py561
-rw-r--r--test/explain/alias-order.py81
-rw-r--r--test/explain/basic.py399
-rw-r--r--test/explain/function-actions.py100
-rw-r--r--test/explain/save-info.py211
-rw-r--r--test/import.py4
-rw-r--r--test/option/debug-count.py10
-rw-r--r--test/option/debug-stacktrace.py4
-rw-r--r--test/option/debug-time.py42
-rw-r--r--test/option/profile.py14
72 files changed, 5200 insertions, 2919 deletions
diff --git a/QMTest/TestSCons.py b/QMTest/TestSCons.py
index 8d820b6..e1884ac 100644
--- a/QMTest/TestSCons.py
+++ b/QMTest/TestSCons.py
@@ -80,7 +80,7 @@ def gccFortranLibs():
for l in stderr.readlines():
list = string.split(l)
if len(list) > 3 and list[:2] == ['gcc', 'version']:
- if list[2][:2] == '3.':
+ if list[2][:2] in ('3.', '4.'):
libs = ['frtbegin'] + libs
break
return libs
@@ -304,14 +304,15 @@ class TestSCons(TestCommon):
# we call test.no_result().
self.no_result(skip=1)
- def diff_substr(self, expect, actual):
+ def diff_substr(self, expect, actual, prelen=20, postlen=40):
i = 0
for x, y in zip(expect, actual):
if x != y:
return "Actual did not match expect at char %d:\n" \
" Expect: %s\n" \
" Actual: %s\n" \
- % (i, repr(expect[i-20:i+40]), repr(actual[i-20:i+40]))
+ % (i, repr(expect[i-prelen:i+postlen]),
+ repr(actual[i-prelen:i+postlen]))
i = i + 1
return "Actual matched the expected output???"
@@ -337,6 +338,13 @@ class TestSCons(TestCommon):
x = string.replace(x, 'line 1,', 'line %s,' % line)
return x
+ def normalize_pdf(self, s):
+ s = re.sub(r'/CreationDate \(D:[^)]*\)',
+ r'/CreationDate (D:XXXX)', s)
+ s = re.sub(r'/ID \[<[0-9a-fA-F]*> <[0-9a-fA-F]*>\]',
+ r'/ID [<XXXX> <XXXX>]', s)
+ return s
+
def java_ENV(self):
"""
Return a default external environment that uses a local Java SDK
@@ -384,6 +392,7 @@ for opt, arg in cmd_opts:
else: opt_string = opt_string + ' ' + opt
for a in args:
contents = open(a, 'rb').read()
+ a = string.replace(a, '\\\\', '\\\\\\\\')
subst = r'{ my_qt_symbol( "' + a + '\\\\n" ); }'
if impl:
contents = re.sub( r'#include.*', '', contents )
@@ -445,7 +454,11 @@ void my_qt_symbol(const char *arg) {
self.write([dir, 'lib', 'SConstruct'], r"""
env = Environment()
-env.SharedLibrary( 'myqt', 'my_qobject.cpp' )
+import sys
+if sys.platform == 'win32':
+ env.StaticLibrary( 'myqt', 'my_qobject.cpp' )
+else:
+ env.SharedLibrary( 'myqt', 'my_qobject.cpp' )
""")
self.run(chdir = self.workpath(dir, 'lib'),
diff --git a/QMTest/TestSCons_time.py b/QMTest/TestSCons_time.py
index 34caa06..1ceb529 100644
--- a/QMTest/TestSCons_time.py
+++ b/QMTest/TestSCons_time.py
@@ -187,6 +187,19 @@ class TestSCons_time(TestCommon):
apply(TestCommon.__init__, [self], kw)
+ # Now that the testing object has been set up, check if we should
+ # skip the test due to the Python version. We need to be able to
+ # import __future__ (which scons-time.py uses for nested scopes)
+ # and to handle list comprehensions (just because we're avoiding
+ # the old map() and filter() idioms).
+
+ try:
+ import __future__
+ except ImportError:
+ version = string.split(sys.version)[0]
+ msg = 'scons-time does not work on Python version %s\n' % version
+ self.skip_test(msg)
+
try:
eval('[x for x in [1, 2]]')
except SyntaxError:
diff --git a/doc/man/scons.1 b/doc/man/scons.1
index c0b91ec..769cfc2 100644
--- a/doc/man/scons.1
+++ b/doc/man/scons.1
@@ -586,7 +586,15 @@ Print how many objects are created
of the various classes used internally by SCons
before and after reading the SConscript files
and before and after building targets.
-This only works when run under Python 2.1 or later.
+This is not supported when run under Python versions earlier than 2.1,
+when SCons is executed with the Python
+.B -O
+(optimized) option,
+or when the SCons modules
+have been compiled with optimization
+(that is, when executing from
+.B *.pyo
+files).
.TP
--debug=dtree
@@ -2027,6 +2035,84 @@ env.other_method_name('another arg')
.EE
'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.TP
+.RI AddOption( arguments )
+This function adds a new command-line option to be recognized.
+The specified
+.I arguments
+are the same as supported by the standard Python
+.B optparse.add_option
+method;
+see the documentation for
+.B optparse
+for a thorough discussion of its option-processing capabities.
+(Note that although the
+.B optparse
+module was not a standard module until Python 2.3,
+.B scons
+contains a compatible version of the module
+that is used to provide identical functionality
+when run by earlier Python versions.)
+
+If no
+.B default=
+keyword argument is supplied when calling
+.BR AddOption (),
+the option will have a default value of
+.BR None .
+
+Once a new command-line option has been added with
+.BR AddOption (),
+the option value may be accessed using
+.BR GetOption ()
+or
+.BR env.GetOption ().
+The value may also be set, using
+.BR SetOption ()
+or
+.BR env.SetOption (),
+if conditions in a
+.B SConscript
+require overriding any default value.
+Note, however, that a
+value specified on the command line will
+.I always
+override a value set by any SConscript file.
+
+Any specified
+.B help=
+strings for the new option(s)
+will be displayed by the
+.B -H
+or
+.B -h
+options
+(the latter only if no other help text is
+specified in the SConscript files).
+The help text for the local options specified by
+.BR AddOption ()
+will appear below the SCons options themselves,
+under a separate
+.B "Local Options"
+heading.
+The options will appear in the help text
+in the order in which the
+.BR AddOption ()
+calls occur.
+
+Example:
+
+.ES
+AddOption('--prefix',
+ dest='prefix',
+ nargs=1, type='string',
+ action='store',
+ metavar='DIR',
+ help='installation prefix')
+env = Environment(PREFIX = GetOption('prefix'))
+.EE
+
+'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.TP
.RI AddPostAction( target ", " action )
.TP
@@ -4110,6 +4196,8 @@ which corresponds to -h and --help;
which corresponds to --implicit-cache;
.B max_drift
which corresponds to --max-drift;
+.B no_exec
+which corresponds to -n, --no-exec, --just-print, --dry-run and --recon;
.B num_jobs
which corresponds to -j and --jobs.
.B random
diff --git a/doc/user/libraries.in b/doc/user/libraries.in
index 9a12062..0fe53b8 100644
--- a/doc/user/libraries.in
+++ b/doc/user/libraries.in
@@ -108,8 +108,6 @@
</para>
- <para>
-
<scons_example name="objects" printme="1">
<file name="SConstruct" printme="1">
Library('foo', ['f1.c', 'f2.o', 'f3.c', 'f4.o'])
diff --git a/doc/user/libraries.sgml b/doc/user/libraries.sgml
index 96814a7..772810f 100644
--- a/doc/user/libraries.sgml
+++ b/doc/user/libraries.sgml
@@ -106,8 +106,6 @@
</para>
- <para>
-
<programlisting>
Library('foo', ['f1.c', 'f2.o', 'f3.c', 'f4.o'])
</programlisting>
diff --git a/src/CHANGES.txt b/src/CHANGES.txt
index 9cf69e9..70519bf 100644
--- a/src/CHANGES.txt
+++ b/src/CHANGES.txt
@@ -36,6 +36,9 @@ RELEASE 0.97.X - XXX
- Support {Get,Set}Option('random') so random-dependency interaction
with CacheDir() is controllable from SConscript files.
+ - Add a new AddOption() function to support user-defined command-
+ line flags (like --prefix=, --force, etc.).
+
- Push and retrieve built symlinks to/from a CacheDir() as actual
symlinks, not by copying the file contents.
@@ -55,6 +58,9 @@ RELEASE 0.97.X - XXX
for adding a new method, respectively, to a construction environment
or an arbitrary object (such as a class).
+ - Fix the --debug=time option when the -j option is specified and all
+ files are up to date.
+
From Leanid Nazdrynau:
- When applying Tool modules after a construction environment has
@@ -66,6 +72,11 @@ RELEASE 0.97.X - XXX
- Find Java anonymous classes when the next token after the name is
an open parenthesis.
+ From Adam Simpkins:
+
+ - Allow worker threads to terminate gracefully when all jobs are
+ finished.
+
From Sohail Somani:
- Add LaTeX scanner support for finding dependencies specified with
diff --git a/src/engine/MANIFEST.in b/src/engine/MANIFEST.in
index 3905a91..4bcd1e0 100644
--- a/src/engine/MANIFEST.in
+++ b/src/engine/MANIFEST.in
@@ -2,9 +2,11 @@ SCons/__init__.py
SCons/Action.py
SCons/Builder.py
SCons/compat/__init__.py
+SCons/compat/_scons_optparse.py
SCons/compat/_scons_sets.py
SCons/compat/_scons_sets15.py
SCons/compat/_scons_subprocess.py
+SCons/compat/_scons_textwrap.py
SCons/compat/_scons_UserString.py
SCons/compat/builtins.py
SCons/Conftest.py
@@ -22,10 +24,6 @@ SCons/Node/__init__.py
SCons/Node/Alias.py
SCons/Node/FS.py
SCons/Node/Python.py
-SCons/Optik/__init__.py
-SCons/Optik/errors.py
-SCons/Optik/option.py
-SCons/Optik/option_parser.py
SCons/Options/__init__.py
SCons/Options/BoolOption.py
SCons/Options/EnumOption.py
@@ -55,6 +53,7 @@ SCons/SConf.py
SCons/SConsign.py
SCons/Script/Main.py
SCons/Script/SConscript.py
+SCons/Script/SConsOptions.py
SCons/Script/__init__.py
SCons/Sig/__init__.py
SCons/Sig/MD5.py
diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py
index 23d02e9..136c3f7 100644
--- a/src/engine/SCons/Defaults.py
+++ b/src/engine/SCons/Defaults.py
@@ -258,91 +258,46 @@ def _concat_ixes(prefix, list, suffix, env):
return result
def _stripixes(prefix, list, suffix, stripprefix, stripsuffix, env, c=None):
- """This is a wrapper around _concat() that checks for the existence
- of prefixes or suffixes on list elements and strips them where it
- finds them. This is used by tools (like the GNU linker) that need
- to turn something like 'libfoo.a' into '-lfoo'."""
+ """
+ This is a wrapper around _concat()/_concat_ixes() that checks for the
+ existence of prefixes or suffixes on list elements and strips them
+ where it finds them. This is used by tools (like the GNU linker)
+ that need to turn something like 'libfoo.a' into '-lfoo'.
+ """
+ if not list:
+ return list
+
if not callable(c):
- if callable(env["_concat"]):
- c = env["_concat"]
+ env_c = env['_concat']
+ if env_c != _concat and callable(env_c):
+ # There's a custom _concat() method in the construction
+ # environment, and we've allowed people to set that in
+ # the past (see test/custom-concat.py), so preserve the
+ # backwards compatibility.
+ c = env_c
else:
- c = _concat
- def f(list, sp=stripprefix, ss=stripsuffix):
- result = []
- for l in list:
- if isinstance(l, SCons.Node.FS.File):
- result.append(l)
- continue
- if not SCons.Util.is_String(l):
- l = str(l)
- if l[:len(sp)] == sp:
- l = l[len(sp):]
- if l[-len(ss):] == ss:
- l = l[:-len(ss)]
- result.append(l)
- return result
- return c(prefix, list, suffix, env, f)
-
-# This is an alternate _stripixes() function that passes all of our tests
-# (as of 21 February 2007), like the current version above. It's more
-# straightforward because it does its manipulation directly, not using
-# the funky f call-back function to _concat(). (In this respect it's
-# like the updated _defines() function below.)
-#
-# The most convoluted thing is that it still uses a custom _concat()
-# function if one was placed in the construction environment; there's
-# a specific test for that functionality, but it might be worth getting
-# rid of.
-#
-# Since this work was done while trying to get 0.97 out the door
-# (just prior to 0.96.96), I decided to be cautious and leave the old
-# function as is, to minimize the chance of other corner-case regressions.
-# The updated version is captured here so we can uncomment it and start
-# using it at a less sensitive time in the development cycle (or when
-# it's clearly required to fix something).
-#
-#def _stripixes(prefix, list, suffix, stripprefix, stripsuffix, env, c=None):
-# """
-# This is a wrapper around _concat()/_concat_ixes() that checks for the
-# existence of prefixes or suffixes on list elements and strips them
-# where it finds them. This is used by tools (like the GNU linker)
-# that need to turn something like 'libfoo.a' into '-lfoo'.
-# """
-#
-# if not list:
-# return list
-#
-# if not callable(c):
-# env_c = env['_concat']
-# if env_c != _concat and callable(env_c):
-# # There's a custom _concat() method in the construction
-# # environment, and we've allowed people to set that in
-# # the past (see test/custom-concat.py), so preserve the
-# # backwards compatibility.
-# c = env_c
-# else:
-# c = _concat_ixes
-#
-# if SCons.Util.is_List(list):
-# list = SCons.Util.flatten(list)
-#
-# lsp = len(stripprefix)
-# lss = len(stripsuffix)
-# stripped = []
-# for l in SCons.PathList.PathList(list).subst_path(env, None, None):
-# if isinstance(l, SCons.Node.FS.File):
-# stripped.append(l)
-# continue
-# if not SCons.Util.is_String(l):
-# l = str(l)
-# if l[:lsp] == stripprefix:
-# l = l[lsp:]
-# if l[-lss:] == stripsuffix:
-# l = l[:-lss]
-# stripped.append(l)
-#
-# return c(prefix, stripped, suffix, env)
+ c = _concat_ixes
+
+ if SCons.Util.is_List(list):
+ list = SCons.Util.flatten(list)
+
+ lsp = len(stripprefix)
+ lss = len(stripsuffix)
+ stripped = []
+ for l in SCons.PathList.PathList(list).subst_path(env, None, None):
+ if isinstance(l, SCons.Node.FS.File):
+ stripped.append(l)
+ continue
+ if not SCons.Util.is_String(l):
+ l = str(l)
+ if l[:lsp] == stripprefix:
+ l = l[lsp:]
+ if l[-lss:] == stripsuffix:
+ l = l[:-lss]
+ stripped.append(l)
+
+ return c(prefix, stripped, suffix, env)
def _defines(prefix, defs, suffix, env, c=_concat_ixes):
"""A wrapper around _concat_ixes that turns a list or string
diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py
index 94cdf74..bffe0ef 100644
--- a/src/engine/SCons/Environment.py
+++ b/src/engine/SCons/Environment.py
@@ -1615,7 +1615,7 @@ class Base(SubstitutionEnvironment):
# Prepend './' so the lookup doesn't interpret an initial
# '#' on the file name portion as meaning the Node should
# be relative to the top-level SConstruct directory.
- target = self.fs.Entry('.'+os.sep+src.name, dnode)
+ target = dnode.Entry('.'+os.sep+src.name)
tgt.extend(InstallBuilder(self, target, src))
return tgt
diff --git a/src/engine/SCons/Job.py b/src/engine/SCons/Job.py
index 9832f14..b28aaaf 100644
--- a/src/engine/SCons/Job.py
+++ b/src/engine/SCons/Job.py
@@ -76,6 +76,9 @@ class Jobs:
signal.signal(signal.SIGINT, signal.SIG_IGN)
raise
+ def cleanup(self):
+ self.job.cleanup()
+
class Serial:
"""This class is used to execute tasks in series, and is more efficient
than Parallel, but is only appropriate for non-parallel builds. Only
@@ -122,6 +125,8 @@ class Serial:
task.postprocess()
+ def cleanup(self):
+ pass
# Trap import failure so that everything in the Job module but the
# Parallel class (and its dependent classes) will work if the interpreter
@@ -148,6 +153,12 @@ else:
while 1:
task = self.requestQueue.get()
+ if not task:
+ # The "None" value is used as a sentinel by
+ # ThreadPool.cleanup(). This indicates that there
+ # are no more tasks, so we should quit.
+ break
+
try:
task.execute()
except KeyboardInterrupt:
@@ -170,8 +181,10 @@ else:
self.resultsQueue = Queue.Queue(0)
# Create worker threads
+ self.workers = []
for _ in range(num):
- Worker(self.requestQueue, self.resultsQueue)
+ worker = Worker(self.requestQueue, self.resultsQueue)
+ self.workers.append(worker)
def put(self, obj):
"""Put task into request queue."""
@@ -182,7 +195,36 @@ else:
return self.resultsQueue.get(block)
def preparation_failed(self, obj):
- self.resultsQueue.put((obj, 0))
+ self.resultsQueue.put((obj, False))
+
+ def cleanup(self):
+ """
+ Shuts down the thread pool, giving each worker thread a
+ chance to shut down gracefully.
+ """
+ # For each worker thread, put a sentinel "None" value
+ # on the requestQueue (indicating that there's no work
+ # to be done) so that each worker thread will get one and
+ # terminate gracefully.
+ for _ in self.workers:
+ self.requestQueue.put(None)
+
+ # Wait for all of the workers to terminate.
+ #
+ # If we don't do this, later Python versions (2.4, 2.5) often
+ # seem to raise exceptions during shutdown. This happens
+ # in requestQueue.get(), as an assertion failure that
+ # requestQueue.not_full is notified while not acquired,
+ # seemingly because the main thread has shut down (or is
+ # in the process of doing so) while the workers are still
+ # trying to pull sentinels off the requestQueue.
+ #
+ # Normally these terminations should happen fairly quickly,
+ # but we'll stick a one-second timeout on here just in case
+ # someone gets hung.
+ for worker in self.workers:
+ worker.join(1.0)
+ self.workers = []
class Parallel:
"""This class is used to execute tasks in parallel, and is somewhat
@@ -261,3 +303,6 @@ else:
if self.tp.resultsQueue.empty():
break
+
+ def cleanup(self):
+ self.tp.cleanup()
diff --git a/src/engine/SCons/JobTests.py b/src/engine/SCons/JobTests.py
index e38e251..5a47da7 100644
--- a/src/engine/SCons/JobTests.py
+++ b/src/engine/SCons/JobTests.py
@@ -121,7 +121,7 @@ class ExceptionTask:
self.was_prepared = 1
def execute(self):
- raise "exception"
+ raise Exception
def executed(self):
self.taskmaster.num_executed = self.taskmaster.num_executed + 1
@@ -381,7 +381,7 @@ class badnode (goodnode):
goodnode.__init__(self)
self.expect_to_be = SCons.Node.failed
def build(self, **kw):
- raise 'badnode exception'
+ raise Exception, 'badnode exception'
class slowbadnode (badnode):
def build(self, **kw):
@@ -390,11 +390,11 @@ class slowbadnode (badnode):
# it is faster than slowgoodnode then these could complete
# while the scheduler is sleeping.
time.sleep(0.05)
- raise 'slowbadnode exception'
+ raise Exception, 'slowbadnode exception'
class badpreparenode (badnode):
def prepare(self):
- raise 'badpreparenode exception'
+ raise Exception, 'badpreparenode exception'
class _SConsTaskTest(unittest.TestCase):
diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py
index c9a1443..152a389 100644
--- a/src/engine/SCons/Node/FS.py
+++ b/src/engine/SCons/Node/FS.py
@@ -572,6 +572,16 @@ class Base(SCons.Node.Node):
self.cwd = None # will hold the SConscript directory for target nodes
self.duplicate = directory.duplicate
+ def must_be_same(self, klass):
+ """
+ This node, which already existed, is being looked up as the
+ specified klass. Raise an exception if it isn't.
+ """
+ if self.__class__ is klass or klass is Entry:
+ return
+ raise TypeError, "Tried to lookup %s '%s' as a %s." %\
+ (self.__class__.__name__, self.path, klass.__name__)
+
def get_dir(self):
return self.dir
@@ -668,8 +678,8 @@ class Base(SCons.Node.Node):
name=self.name
while dir:
if dir.srcdir:
- srcnode = self.fs.Entry(name, dir.srcdir,
- klass=self.__class__)
+ srcnode = dir.srcdir.Entry(name)
+ srcnode.must_be_same(self.__class__)
return srcnode
name = dir.name + os.sep + name
dir = dir.up()
@@ -848,12 +858,13 @@ class Entry(Base):
else:
return self.get_contents()
- def must_be_a_Dir(self):
+ def must_be_same(self, klass):
"""Called to make sure a Node is a Dir. Since we're an
Entry, we can morph into one."""
- self.__class__ = Dir
- self._morph()
- return self
+ if not self.__class__ is klass:
+ self.__class__ = klass
+ self._morph()
+ self.clear
# The following methods can get called before the Taskmaster has
# had a chance to call disambiguate() directly to see if this Entry
@@ -1011,16 +1022,6 @@ class FS(LocalFS):
def getcwd(self):
return self._cwd
- def __checkClass(self, node, klass):
- if isinstance(node, klass) or klass == Entry:
- return node
- if node.__class__ == Entry:
- node.__class__ = klass
- node._morph()
- return node
- raise TypeError, "Tried to lookup %s '%s' as a %s." % \
- (node.__class__.__name__, node.path, klass.__name__)
-
def _doLookup_key(self, fsclass, name, directory = None, create = 1):
return (fsclass, name, directory)
@@ -1099,7 +1100,7 @@ class FS(LocalFS):
# We tried to look up the entry in either an Entry or
# a File. Give whatever it is a chance to do what's
# appropriate: morph into a Dir or raise an exception.
- directory.must_be_a_Dir()
+ directory.must_be_same(Dir)
entries = directory.entries
try:
directory = entries[norm]
@@ -1117,7 +1118,7 @@ class FS(LocalFS):
directory.add_wkid(d)
directory = d
- directory.must_be_a_Dir()
+ directory.must_be_same(Dir)
try:
e = directory.entries[last_norm]
@@ -1135,7 +1136,8 @@ class FS(LocalFS):
directory.entries[last_norm] = result
directory.add_wkid(result)
else:
- result = self.__checkClass(e, fsclass)
+ e.must_be_same(fsclass)
+ result = e
memo_dict[memo_key] = result
@@ -1151,15 +1153,18 @@ class FS(LocalFS):
If directory is None, and name is a relative path,
then the same applies.
"""
- if not SCons.Util.is_String(name):
- # This handles cases where the object is a Proxy wrapping
- # a Node.FS.File object (e.g.). It would be good to handle
- # this more directly some day by having the callers of this
- # function recognize that a Proxy can be treated like the
- # underlying object (that is, get rid of the isinstance()
- # calls that explicitly look for a Node.FS.Base object).
+ try:
+ # Decide if this is a top-relative look up. The normal case
+ # (by far) is handed a non-zero-length string to look up,
+ # so just (try to) check for the initial '#'.
+ top_relative = (name[0] == '#')
+ except (AttributeError, IndexError):
+ # The exceptions we may encounter in unusual cases:
+ # AttributeError: a proxy without a __getitem__() method.
+ # IndexError: a null string.
+ top_relative = False
name = str(name)
- if name and name[0] == '#':
+ if top_relative:
directory = self.Top
name = name[1:]
if name and (name[0] == os.sep or name[0] == '/'):
@@ -1199,7 +1204,8 @@ class FS(LocalFS):
klass = Entry
if isinstance(name, Base):
- return self.__checkClass(name, klass)
+ name.must_be_same(klass)
+ return name
else:
if directory and not isinstance(directory, Dir):
directory = self.Dir(directory)
@@ -1628,11 +1634,6 @@ class Dir(Base):
def entry_tpath(self, name):
return self.tpath + os.sep + name
- def must_be_a_Dir(self):
- """Called to make sure a Node is a Dir. Since we're already
- one, this is a no-op for us."""
- return self
-
def entry_exists_on_disk(self, name):
try:
d = self.on_disk_entries
@@ -1777,6 +1778,11 @@ class RootDir(Dir):
self.tpath = name + os.sep
self._morph()
+ def must_be_same(self, klass):
+ if klass is Dir:
+ return
+ Base.must_be_same(self, klass)
+
def __str__(self):
return self.abspath
@@ -1856,14 +1862,25 @@ class FileBuildInfo(SCons.Node.BuildInfoBase):
as dependency info. Convert the strings to actual Nodes (for
use by the --debug=explain code and --implicit-cache).
"""
- Entry_func = self.node.dir.Entry
+ def str_to_node(s, entry=self.node.dir.Entry):
+ # This is a little bogus; we're going to mimic the lookup
+ # order of env.arg2nodes() by hard-coding an Alias lookup
+ # before we assume it's an Entry. This should be able to
+ # go away once the Big Signature Refactoring pickles the
+ # actual NodeInfo object, which will let us know precisely
+ # what type of Node to turn it into.
+ import SCons.Node.Alias
+ n = SCons.Node.Alias.default_ans.lookup(s)
+ if not n:
+ n = entry(s)
+ return n
for attr in ['bsources', 'bdepends', 'bimplicit']:
try:
val = getattr(self, attr)
except AttributeError:
pass
else:
- setattr(self, attr, map(Entry_func, val))
+ setattr(self, attr, map(str_to_node, val))
def format(self):
result = [ self.ninfo.format() ]
bkids = self.bsources + self.bdepends + self.bimplicit
@@ -1899,12 +1916,12 @@ class File(Base):
def Entry(self, name):
"""Create an entry node named 'name' relative to
the SConscript directory of this file."""
- return self.fs.Entry(name, self.cwd)
+ return self.cwd.Entry(name)
def Dir(self, name):
"""Create a directory node named 'name' relative to
the SConscript directory of this file."""
- return self.fs.Dir(name, self.cwd)
+ return self.cwd.Dir(name)
def Dirs(self, pathlist):
"""Create a list of directories relative to the SConscript
@@ -1914,7 +1931,7 @@ class File(Base):
def File(self, name):
"""Create a file node named 'name' relative to
the SConscript directory of this file."""
- return self.fs.File(name, self.cwd)
+ return self.cwd.File(name)
#def generate_build_dict(self):
# """Return an appropriate dictionary of values for building
@@ -2032,7 +2049,7 @@ class File(Base):
Note that there's a special trick here with the execute flag
(one that's not normally done for other actions). Basically
- if the user requested a noexec (-n) build, then
+ if the user requested a no_exec (-n) build, then
SCons.Action.execute_actions is set to 0 and when any action
is called, it does its showing but then just returns zero
instead of actually calling the action execution operation.
@@ -2334,11 +2351,6 @@ class File(Base):
dir = os.path.join(self.fs.CachePath, subdir)
return dir, os.path.join(dir, cache_sig)
- def must_be_a_Dir(self):
- """Called to make sure a Node is a Dir. Since we're already a
- File, this is a TypeError..."""
- raise TypeError, "Tried to lookup File '%s' as a Dir." % self.path
-
default_fs = None
class FileFinder:
diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py
index b9971a2..0b60fb9 100644
--- a/src/engine/SCons/Node/FSTests.py
+++ b/src/engine/SCons/Node/FSTests.py
@@ -1528,7 +1528,7 @@ class DirTestCase(_tempdirTestCase):
x = e.get_executor()
x.add_pre_action('pre')
x.add_post_action('post')
- e.must_be_a_Dir()
+ e.must_be_same(SCons.Node.FS.Dir)
a = x.get_action_list()
assert a[0] == 'pre', a
assert a[2] == 'post', a
diff --git a/src/engine/SCons/Optik/__init__.py b/src/engine/SCons/Optik/__init__.py
deleted file mode 100644
index 8ea41fb..0000000
--- a/src/engine/SCons/Optik/__init__.py
+++ /dev/null
@@ -1,32 +0,0 @@
-"""optik
-
-A powerful, extensible, and easy-to-use command-line parser for Python.
-
-By Greg Ward <gward@python.net>
-
-See http://optik.sourceforge.net/
-"""
-
-# Copyright (c) 2001 Gregory P. Ward. All rights reserved.
-# See the README.txt distributed with Optik for licensing terms.
-
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-
-# Original Optik revision this is based on:
-__Optik_revision__ = "__init__.py,v 1.11 2002/04/11 19:17:34 gward Exp"
-
-__version__ = "1.3"
-
-
-# Re-import these for convenience
-from SCons.Optik.option import Option
-from SCons.Optik.option_parser import \
- OptionParser, SUPPRESS_HELP, SUPPRESS_USAGE
-from SCons.Optik.errors import OptionValueError
-
-
-# Some day, there might be many Option classes. As of Optik 1.3, the
-# preferred way to instantiate Options is indirectly, via make_option(),
-# which will become a factory function when there are many Option
-# classes.
-make_option = Option
diff --git a/src/engine/SCons/Optik/errors.py b/src/engine/SCons/Optik/errors.py
deleted file mode 100644
index dca8a69..0000000
--- a/src/engine/SCons/Optik/errors.py
+++ /dev/null
@@ -1,55 +0,0 @@
-"""optik.errors
-
-Exception classes used by Optik.
-"""
-
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-
-# Original Optik revision this is based on:
-__Optik_revision__ = "errors.py,v 1.5 2002/02/13 23:29:47 gward Exp"
-
-# Copyright (c) 2001 Gregory P. Ward. All rights reserved.
-# See the README.txt distributed with Optik for licensing terms.
-
-# created 2001/10/17 GPW (from optik.py)
-
-
-class OptikError (Exception):
- def __init__ (self, msg):
- self.msg = msg
-
- def __str__ (self):
- return self.msg
-
-
-class OptionError (OptikError):
- """
- Raised if an Option instance is created with invalid or
- inconsistent arguments.
- """
-
- def __init__ (self, msg, option):
- self.msg = msg
- self.option_id = str(option)
-
- def __str__ (self):
- if self.option_id:
- return "option %s: %s" % (self.option_id, self.msg)
- else:
- return self.msg
-
-class OptionConflictError (OptionError):
- """
- Raised if conflicting options are added to an OptionParser.
- """
-
-class OptionValueError (OptikError):
- """
- Raised if an invalid option value is encountered on the command
- line.
- """
-
-class BadOptionError (OptikError):
- """
- Raised if an invalid or ambiguous option is seen on the command-line.
- """
diff --git a/src/engine/SCons/Optik/option.py b/src/engine/SCons/Optik/option.py
deleted file mode 100644
index 7493205..0000000
--- a/src/engine/SCons/Optik/option.py
+++ /dev/null
@@ -1,388 +0,0 @@
-"""optik.option
-
-Defines the Option class and some standard value-checking functions.
-"""
-
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-
-# Original Optik revision this is based on:
-__Optik_revision__ = "option.py,v 1.19.2.1 2002/07/23 01:51:14 gward Exp"
-
-# Copyright (c) 2001 Gregory P. Ward. All rights reserved.
-# See the README.txt distributed with Optik for licensing terms.
-
-# created 2001/10/17, GPW (from optik.py)
-
-import sys
-import string
-from types import TupleType, ListType, DictType
-from SCons.Optik.errors import OptionError, OptionValueError
-
-_builtin_cvt = { "int" : (int, "integer"),
- "long" : (long, "long integer"),
- "float" : (float, "floating-point"),
- "complex" : (complex, "complex") }
-
-def check_builtin (option, opt, value):
- (cvt, what) = _builtin_cvt[option.type]
- try:
- return cvt(value)
- except ValueError:
- raise OptionValueError(
- #"%s: invalid %s argument %s" % (opt, what, repr(value)))
- "option %s: invalid %s value: %s" % (opt, what, repr(value)))
-
-def check_choice(option, opt, value):
- if value in option.choices:
- return value
- else:
- choices = string.join(map(repr, option.choices),", ")
- raise OptionValueError(
- "option %s: invalid choice: %s (choose from %s)"
- % (opt, repr(value), choices))
-
-# Not supplying a default is different from a default of None,
-# so we need an explicit "not supplied" value.
-NO_DEFAULT = "NO"+"DEFAULT"
-
-
-class Option:
- """
- Instance attributes:
- _short_opts : [string]
- _long_opts : [string]
-
- action : string
- type : string
- dest : string
- default : any
- nargs : int
- const : any
- choices : [string]
- callback : function
- callback_args : (any*)
- callback_kwargs : { string : any }
- help : string
- metavar : string
- """
-
- # The list of instance attributes that may be set through
- # keyword args to the constructor.
- ATTRS = ['action',
- 'type',
- 'dest',
- 'default',
- 'nargs',
- 'const',
- 'choices',
- 'callback',
- 'callback_args',
- 'callback_kwargs',
- 'help',
- 'metavar']
-
- # The set of actions allowed by option parsers. Explicitly listed
- # here so the constructor can validate its arguments.
- ACTIONS = ("store",
- "store_const",
- "store_true",
- "store_false",
- "append",
- "count",
- "callback",
- "help",
- "version")
-
- # The set of actions that involve storing a value somewhere;
- # also listed just for constructor argument validation. (If
- # the action is one of these, there must be a destination.)
- STORE_ACTIONS = ("store",
- "store_const",
- "store_true",
- "store_false",
- "append",
- "count")
-
- # The set of actions for which it makes sense to supply a value
- # type, ie. where we expect an argument to this option.
- TYPED_ACTIONS = ("store",
- "append",
- "callback")
-
- # The set of known types for option parsers. Again, listed here for
- # constructor argument validation.
- TYPES = ("string", "int", "long", "float", "complex", "choice")
-
- # Dictionary of argument checking functions, which convert and
- # validate option arguments according to the option type.
- #
- # Signature of checking functions is:
- # check(option : Option, opt : string, value : string) -> any
- # where
- # option is the Option instance calling the checker
- # opt is the actual option seen on the command-line
- # (eg. "-a", "--file")
- # value is the option argument seen on the command-line
- #
- # The return value should be in the appropriate Python type
- # for option.type -- eg. an integer if option.type == "int".
- #
- # If no checker is defined for a type, arguments will be
- # unchecked and remain strings.
- TYPE_CHECKER = { "int" : check_builtin,
- "long" : check_builtin,
- "float" : check_builtin,
- "complex" : check_builtin,
- "choice" : check_choice,
- }
-
-
- # CHECK_METHODS is a list of unbound method objects; they are called
- # by the constructor, in order, after all attributes are
- # initialized. The list is created and filled in later, after all
- # the methods are actually defined. (I just put it here because I
- # like to define and document all class attributes in the same
- # place.) Subclasses that add another _check_*() method should
- # define their own CHECK_METHODS list that adds their check method
- # to those from this class.
- CHECK_METHODS = None
-
-
- # -- Constructor/initialization methods ----------------------------
-
- def __init__ (self, *opts, **attrs):
- # Set _short_opts, _long_opts attrs from 'opts' tuple
- opts = self._check_opt_strings(opts)
- self._set_opt_strings(opts)
-
- # Set all other attrs (action, type, etc.) from 'attrs' dict
- self._set_attrs(attrs)
-
- # Check all the attributes we just set. There are lots of
- # complicated interdependencies, but luckily they can be farmed
- # out to the _check_*() methods listed in CHECK_METHODS -- which
- # could be handy for subclasses! The one thing these all share
- # is that they raise OptionError if they discover a problem.
- for checker in self.CHECK_METHODS:
- checker(self)
-
- def _check_opt_strings (self, opts):
- # Filter out None because early versions of Optik had exactly
- # one short option and one long option, either of which
- # could be None.
- opts = filter(None, opts)
- if not opts:
- raise OptionError("at least one option string must be supplied",
- self)
- return opts
-
- def _set_opt_strings (self, opts):
- self._short_opts = []
- self._long_opts = []
- for opt in opts:
- if len(opt) < 2:
- raise OptionError(
- "invalid option string %s: "
- "must be at least two characters long" % (`opt`,), self)
- elif len(opt) == 2:
- if not (opt[0] == "-" and opt[1] != "-"):
- raise OptionError(
- "invalid short option string %s: "
- "must be of the form -x, (x any non-dash char)" % (`opt`,),
- self)
- self._short_opts.append(opt)
- else:
- if not (opt[0:2] == "--" and opt[2] != "-"):
- raise OptionError(
- "invalid long option string %s: "
- "must start with --, followed by non-dash" % (`opt`,),
- self)
- self._long_opts.append(opt)
-
- def _set_attrs (self, attrs):
- for attr in self.ATTRS:
- if attrs.has_key(attr):
- setattr(self, attr, attrs[attr])
- del attrs[attr]
- else:
- if attr == 'default':
- setattr(self, attr, NO_DEFAULT)
- else:
- setattr(self, attr, None)
- if attrs:
- raise OptionError(
- "invalid keyword arguments: %s" % string.join(attrs.keys(),", "),
- self)
-
-
- # -- Constructor validation methods --------------------------------
-
- def _check_action (self):
- if self.action is None:
- self.action = "store"
- elif self.action not in self.ACTIONS:
- raise OptionError("invalid action: %s" % (`self.action`,), self)
-
- def _check_type (self):
- if self.type is None:
- # XXX should factor out another class attr here: list of
- # actions that *require* a type
- if self.action in ("store", "append"):
- if self.choices is not None:
- # The "choices" attribute implies "choice" type.
- self.type = "choice"
- else:
- # No type given? "string" is the most sensible default.
- self.type = "string"
- else:
- if self.type not in self.TYPES:
- raise OptionError("invalid option type: %s" % (`self.type`,), self)
- if self.action not in self.TYPED_ACTIONS:
- raise OptionError(
- "must not supply a type for action %s" % (`self.action`,), self)
-
- def _check_choice(self):
- if self.type == "choice":
- if self.choices is None:
- raise OptionError(
- "must supply a list of choices for type 'choice'", self)
- elif type(self.choices) not in (TupleType, ListType):
- raise OptionError(
- "choices must be a list of strings ('%s' supplied)"
- % string.split(str(type(self.choices)),"'")[1], self)
- elif self.choices is not None:
- raise OptionError(
- "must not supply choices for type %s" % (repr(self.type),), self)
-
- def _check_dest (self):
- if self.action in self.STORE_ACTIONS and self.dest is None:
- # No destination given, and we need one for this action.
- # Glean a destination from the first long option string,
- # or from the first short option string if no long options.
- if self._long_opts:
- # eg. "--foo-bar" -> "foo_bar"
- self.dest = string.replace(self._long_opts[0][2:],'-', '_')
- else:
- self.dest = self._short_opts[0][1]
-
- def _check_const (self):
- if self.action != "store_const" and self.const is not None:
- raise OptionError(
- "'const' must not be supplied for action %s" % (repr(self.action),),
- self)
-
- def _check_nargs (self):
- if self.action in self.TYPED_ACTIONS:
- if self.nargs is None:
- self.nargs = 1
- elif self.nargs is not None:
- raise OptionError(
- "'nargs' must not be supplied for action %s" % (repr(self.action),),
- self)
-
- def _check_callback (self):
- if self.action == "callback":
- if not callable(self.callback):
- raise OptionError(
- "callback not callable: %s" % (repr(self.callback),), self)
- if (self.callback_args is not None and
- type(self.callback_args) is not TupleType):
- raise OptionError(
- "callback_args, if supplied, must be a tuple: not %s"
- % (repr(self.callback_args),), self)
- if (self.callback_kwargs is not None and
- type(self.callback_kwargs) is not DictType):
- raise OptionError(
- "callback_kwargs, if supplied, must be a dict: not %s"
- % (repr(self.callback_kwargs),), self)
- else:
- if self.callback is not None:
- raise OptionError(
- "callback supplied (%s) for non-callback option"
- % (repr(self.callback),), self)
- if self.callback_args is not None:
- raise OptionError(
- "callback_args supplied for non-callback option", self)
- if self.callback_kwargs is not None:
- raise OptionError(
- "callback_kwargs supplied for non-callback option", self)
-
-
- CHECK_METHODS = [_check_action,
- _check_type,
- _check_choice,
- _check_dest,
- _check_const,
- _check_nargs,
- _check_callback]
-
-
- # -- Miscellaneous methods -----------------------------------------
-
- def __str__ (self):
- if self._short_opts or self._long_opts:
- return string.join(self._short_opts + self._long_opts,"/")
- else:
- raise RuntimeError, "short_opts and long_opts both empty!"
-
- def takes_value (self):
- return self.type is not None
-
-
- # -- Processing methods --------------------------------------------
-
- def check_value (self, opt, value):
- checker = self.TYPE_CHECKER.get(self.type)
- if checker is None:
- return value
- else:
- return checker(self, opt, value)
-
- def process (self, opt, value, values, parser):
-
- # First, convert the value(s) to the right type. Howl if any
- # value(s) are bogus.
- if value is not None:
- if self.nargs == 1:
- value = self.check_value(opt, value)
- else:
- def cv(v,check=self.check_value,o=opt):
- return check(o,v)
-
- value = tuple(map(cv,value))
-
- # And then take whatever action is expected of us.
- # This is a separate method to make life easier for
- # subclasses to add new actions.
- return self.take_action(
- self.action, self.dest, opt, value, values, parser)
-
- def take_action (self, action, dest, opt, value, values, parser):
- if action == "store":
- setattr(values, dest, value)
- elif action == "store_const":
- setattr(values, dest, self.const)
- elif action == "store_true":
- setattr(values, dest, 1)
- elif action == "store_false":
- setattr(values, dest, 0)
- elif action == "append":
- values.ensure_value(dest, []).append(value)
- elif action == "count":
- setattr(values, dest, values.ensure_value(dest, 0) + 1)
- elif action == "callback":
- args = self.callback_args or ()
- kwargs = self.callback_kwargs or {}
- apply( self.callback, (self, opt, value, parser,)+ args, kwargs)
- elif action == "help":
- parser.print_help()
- sys.exit(0)
- elif action == "version":
- parser.print_version()
- sys.exit(0)
- else:
- raise RuntimeError, "unknown action %s" % (repr(self.action),)
-
- return 1
-
-# class Option
diff --git a/src/engine/SCons/Optik/option_parser.py b/src/engine/SCons/Optik/option_parser.py
deleted file mode 100644
index 49b1874..0000000
--- a/src/engine/SCons/Optik/option_parser.py
+++ /dev/null
@@ -1,730 +0,0 @@
-"""optik.option_parser
-
-Provides the OptionParser and Values classes.
-"""
-
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-
-# Original Optik revision this is based on:
-__Optik_revision__ = "option_parser.py,v 1.38.2.1 2002/07/23 01:51:14 gward Exp"
-
-# Copyright (c) 2001 Gregory P. Ward. All rights reserved.
-# See the README.txt distributed with Optik for licensing terms.
-
-# created 2001/10/17, GPW (from optik.py)
-
-import sys, os
-import string
-import types
-from SCons.Optik.option import Option, NO_DEFAULT
-from SCons.Optik.errors import OptionConflictError, OptionValueError, BadOptionError
-
-def get_prog_name ():
- return os.path.basename(sys.argv[0])
-
-
-SUPPRESS_HELP = "SUPPRESS"+"HELP"
-SUPPRESS_USAGE = "SUPPRESS"+"USAGE"
-
-class Values:
-
- def __init__ (self, defaults=None):
- if defaults:
- for (attr, val) in defaults.items():
- setattr(self, attr, val)
-
-
- def _update_careful (self, dict):
- """
- Update the option values from an arbitrary dictionary, but only
- use keys from dict that already have a corresponding attribute
- in self. Any keys in dict without a corresponding attribute
- are silently ignored.
- """
- for attr in dir(self):
- if dict.has_key(attr):
- dval = dict[attr]
- if dval is not None:
- setattr(self, attr, dval)
-
- def _update_loose (self, dict):
- """
- Update the option values from an arbitrary dictionary,
- using all keys from the dictionary regardless of whether
- they have a corresponding attribute in self or not.
- """
- self.__dict__.update(dict)
-
- def _update (self, dict, mode):
- if mode == "careful":
- self._update_careful(dict)
- elif mode == "loose":
- self._update_loose(dict)
- else:
- raise ValueError, "invalid update mode: %s" % (repr(mode),)
-
- def read_module (self, modname, mode="careful"):
- __import__(modname)
- mod = sys.modules[modname]
- self._update(vars(mod), mode)
-
- def read_file (self, filename, mode="careful"):
- vars = {}
- execfile(filename, vars)
- self._update(vars, mode)
-
- def ensure_value (self, attr, value):
- if not hasattr(self, attr) or getattr(self, attr) is None:
- setattr(self, attr, value)
- return getattr(self, attr)
-
-
-class OptionParser:
- """
- Class attributes:
- standard_option_list : [Option]
- list of standard options that will be accepted by all instances
- of this parser class (intended to be overridden by subclasses).
-
- Instance attributes:
- usage : string
- a usage string for your program. Before it is displayed
- to the user, "%prog" will be expanded to the name of
- your program (os.path.basename(sys.argv[0])).
- option_list : [Option]
- the list of all options accepted on the command-line of
- this program
- _short_opt : { string : Option }
- dictionary mapping short option strings, eg. "-f" or "-X",
- to the Option instances that implement them. If an Option
- has multiple short option strings, it will appears in this
- dictionary multiple times.
- _long_opt : { string : Option }
- dictionary mapping long option strings, eg. "--file" or
- "--exclude", to the Option instances that implement them.
- Again, a given Option can occur multiple times in this
- dictionary.
- defaults : { string : any }
- dictionary mapping option destination names to default
- values for each destination.
-
- allow_interspersed_args : boolean = true
- if true, positional arguments may be interspersed with options.
- Assuming -a and -b each take a single argument, the command-line
- -ablah foo bar -bboo baz
- will be interpreted the same as
- -ablah -bboo -- foo bar baz
- If this flag were false, that command line would be interpreted as
- -ablah -- foo bar -bboo baz
- -- ie. we stop processing options as soon as we see the first
- non-option argument. (This is the tradition followed by
- Python's getopt module, Perl's Getopt::Std, and other argument-
- parsing libraries, but it is generally annoying to users.)
-
- rargs : [string]
- the argument list currently being parsed. Only set when
- parse_args() is active, and continually trimmed down as
- we consume arguments. Mainly there for the benefit of
- callback options.
- largs : [string]
- the list of leftover arguments that we have skipped while
- parsing options. If allow_interspersed_args is false, this
- list is always empty.
- values : Values
- the set of option values currently being accumulated. Only
- set when parse_args() is active. Also mainly for callbacks.
-
- Because of the 'rargs', 'largs', and 'values' attributes,
- OptionParser is not thread-safe. If, for some perverse reason, you
- need to parse command-line arguments simultaneously in different
- threads, use different OptionParser instances.
-
- """
-
- standard_option_list = []
-
-
- def __init__ (self,
- usage=None,
- option_list=None,
- option_class=Option,
- version=None,
- conflict_handler="error"):
- self.set_usage(usage)
- self.option_class = option_class
- self.version = version
- self.set_conflict_handler(conflict_handler)
- self.allow_interspersed_args = 1
-
- # Create the various lists and dicts that constitute the
- # "option list". See class docstring for details about
- # each attribute.
- self._create_option_list()
-
- # Populate the option list; initial sources are the
- # standard_option_list class attribute, the 'option_list'
- # argument, and the STD_VERSION_OPTION global (if 'version'
- # supplied).
- self._populate_option_list(option_list)
-
- self._init_parsing_state()
-
- # -- Private methods -----------------------------------------------
- # (used by the constructor)
-
- def _create_option_list (self):
- self.option_list = []
- self._short_opt = {} # single letter -> Option instance
- self._long_opt = {} # long option -> Option instance
- self.defaults = {} # maps option dest -> default value
-
- def _populate_option_list (self, option_list):
- if self.standard_option_list:
- self.add_options(self.standard_option_list)
- if option_list:
- self.add_options(option_list)
-
- def _init_parsing_state (self):
- # These are set in parse_args() for the convenience of callbacks.
- self.rargs = None
- self.largs = None
- self.values = None
-
-
- # -- Simple modifier methods ---------------------------------------
-
- def set_usage (self, usage):
- if usage is None:
- self.usage = "usage: %prog [options]"
- elif usage is SUPPRESS_USAGE:
- self.usage = None
- else:
- self.usage = usage
-
- def enable_interspersed_args (self):
- self.allow_interspersed_args = 1
-
- def disable_interspersed_args (self):
- self.allow_interspersed_args = 0
-
- def set_conflict_handler (self, handler):
- if handler not in ("ignore", "error", "resolve"):
- raise ValueError, "invalid conflict_resolution value %s" % (repr(handler),)
- self.conflict_handler = handler
-
- def set_default (self, dest, value):
- self.defaults[dest] = value
-
- def set_defaults (self, **kwargs):
- self.defaults.update(kwargs)
-
- def get_default_values(self):
- return Values(self.defaults)
-
-
- # -- Option-adding methods -----------------------------------------
-
- def _check_conflict (self, option):
- conflict_opts = []
- for opt in option._short_opts:
- if self._short_opt.has_key(opt):
- conflict_opts.append((opt, self._short_opt[opt]))
- for opt in option._long_opts:
- if self._long_opt.has_key(opt):
- conflict_opts.append((opt, self._long_opt[opt]))
-
- if conflict_opts:
- handler = self.conflict_handler
- if handler == "ignore": # behaviour for Optik 1.0, 1.1
- pass
- elif handler == "error": # new in 1.2
- raise OptionConflictError(
- "conflicting option string(s): %s"
- % string.join( map( lambda x: x[0], conflict_opts),", "),
- option)
- elif handler == "resolve": # new in 1.2
- for (opt, c_option) in conflict_opts:
- if len(opt)>2 and opt[:2]=="--":
- c_option._long_opts.remove(opt)
- del self._long_opt[opt]
- else:
- c_option._short_opts.remove(opt)
- del self._short_opt[opt]
- if not (c_option._short_opts or c_option._long_opts):
- self.option_list.remove(c_option)
-
-
- def add_option (self, *args, **kwargs):
- """add_option(Option)
- add_option(opt_str, ..., kwarg=val, ...)
- """
- if type(args[0]) is types.StringType:
- option = apply(self.option_class,args, kwargs)
- elif len(args) == 1 and not kwargs:
- option = args[0]
- if not isinstance(option, Option):
- raise TypeError, "not an Option instance: %s" % (repr(option),)
- else:
- raise TypeError, "invalid arguments"
-
- self._check_conflict(option)
-
- self.option_list.append(option)
- for opt in option._short_opts:
- self._short_opt[opt] = option
- for opt in option._long_opts:
- self._long_opt[opt] = option
-
- if option.dest is not None: # option has a dest, we need a default
- if option.default is not NO_DEFAULT:
- self.defaults[option.dest] = option.default
- elif not self.defaults.has_key(option.dest):
- self.defaults[option.dest] = None
-
- def add_options (self, option_list):
- for option in option_list:
- self.add_option(option)
-
-
- # -- Option query/removal methods ----------------------------------
-
- def get_option (self, opt_str):
- return (self._short_opt.get(opt_str) or
- self._long_opt.get(opt_str))
-
- def has_option (self, opt_str):
- return (self._short_opt.has_key(opt_str) or
- self._long_opt.has_key(opt_str))
-
-
- def remove_option (self, opt_str):
- option = self._short_opt.get(opt_str)
- if option is None:
- option = self._long_opt.get(opt_str)
- if option is None:
- raise ValueError("no such option %s" % (repr(opt_str),))
-
- for opt in option._short_opts:
- del self._short_opt[opt]
- for opt in option._long_opts:
- del self._long_opt[opt]
- self.option_list.remove(option)
-
-
- # -- Option-parsing methods ----------------------------------------
-
- def _get_args (self, args):
- if args is None:
- return sys.argv[1:]
- else:
- return args[:] # don't modify caller's list
-
- def parse_args (self, args=None, values=None):
- """
- parse_args(args : [string] = sys.argv[1:],
- values : Values = None)
- -> (values : Values, args : [string])
-
- Parse the command-line options found in 'args' (default:
- sys.argv[1:]). Any errors result in a call to 'error()', which
- by default prints the usage message to stderr and calls
- sys.exit() with an error message. On success returns a pair
- (values, args) where 'values' is an Values instance (with all
- your option values) and 'args' is the list of arguments left
- over after parsing options.
- """
- rargs = self._get_args(args)
- if values is None:
- values = self.get_default_values()
-
- # Store the halves of the argument list as attributes for the
- # convenience of callbacks:
- # rargs
- # the rest of the command-line (the "r" stands for
- # "remaining" or "right-hand")
- # largs
- # the leftover arguments -- ie. what's left after removing
- # options and their arguments (the "l" stands for "leftover"
- # or "left-hand")
- self.rargs = rargs
- self.largs = largs = []
- self.values = values
-
- try:
- stop = self._process_args(largs, rargs, values)
- except (BadOptionError, OptionValueError), err:
- self.error(err.msg)
-
- args = largs + rargs
- return self.check_values(values, args)
-
- def check_values (self, values, args):
- """
- check_values(values : Values, args : [string])
- -> (values : Values, args : [string])
-
- Check that the supplied option values and leftover arguments are
- valid. Returns the option values and leftover arguments
- (possibly adjusted, possibly completely new -- whatever you
- like). Default implementation just returns the passed-in
- values; subclasses may override as desired.
- """
- return (values, args)
-
- def _process_args (self, largs, rargs, values):
- """_process_args(largs : [string],
- rargs : [string],
- values : Values)
-
- Process command-line arguments and populate 'values', consuming
- options and arguments from 'rargs'. If 'allow_interspersed_args' is
- false, stop at the first non-option argument. If true, accumulate any
- interspersed non-option arguments in 'largs'.
- """
- while rargs:
- arg = rargs[0]
- # We handle bare "--" explicitly, and bare "-" is handled by the
- # standard arg handler since the short arg case ensures that the
- # len of the opt string is greater than 1.
- if arg == "--":
- del rargs[0]
- return
- elif arg[0:2] == "--":
- # process a single long option (possibly with value(s))
- self._process_long_opt(rargs, values)
- elif arg[:1] == "-" and len(arg) > 1:
- # process a cluster of short options (possibly with
- # value(s) for the last one only)
- self._process_short_opts(rargs, values)
- elif self.allow_interspersed_args:
- largs.append(arg)
- del rargs[0]
- else:
- return # stop now, leave this arg in rargs
-
- # Say this is the original argument list:
- # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)]
- # ^
- # (we are about to process arg(i)).
- #
- # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of
- # [arg0, ..., arg(i-1)] (any options and their arguments will have
- # been removed from largs).
- #
- # The while loop will usually consume 1 or more arguments per pass.
- # If it consumes 1 (eg. arg is an option that takes no arguments),
- # then after _process_arg() is done the situation is:
- #
- # largs = subset of [arg0, ..., arg(i)]
- # rargs = [arg(i+1), ..., arg(N-1)]
- #
- # If allow_interspersed_args is false, largs will always be
- # *empty* -- still a subset of [arg0, ..., arg(i-1)], but
- # not a very interesting subset!
-
- def _match_long_opt (self, opt):
- """_match_long_opt(opt : string) -> string
-
- Determine which long option string 'opt' matches, ie. which one
- it is an unambiguous abbrevation for. Raises BadOptionError if
- 'opt' doesn't unambiguously match any long option string.
- """
- return _match_abbrev(opt, self._long_opt)
-
- def _process_long_opt (self, rargs, values):
- arg = rargs.pop(0)
-
- # Value explicitly attached to arg? Pretend it's the next
- # argument.
- if "=" in arg:
- (opt, next_arg) = string.split(arg,"=", 1)
- rargs.insert(0, next_arg)
- had_explicit_value = 1
- else:
- opt = arg
- had_explicit_value = 0
-
- opt = self._match_long_opt(opt)
- option = self._long_opt[opt]
- if option.takes_value():
- nargs = option.nargs
- if len(rargs) < nargs:
- if nargs == 1:
- self.error("%s option requires a value" % opt)
- else:
- self.error("%s option requires %d values"
- % (opt, nargs))
- elif nargs == 1:
- value = rargs.pop(0)
- else:
- value = tuple(rargs[0:nargs])
- del rargs[0:nargs]
-
- elif had_explicit_value:
- self.error("%s option does not take a value" % opt)
-
- else:
- value = None
-
- option.process(opt, value, values, self)
-
- def _process_short_opts (self, rargs, values):
- arg = rargs.pop(0)
- stop = 0
- i = 1
- for ch in arg[1:]:
- opt = "-" + ch
- option = self._short_opt.get(opt)
- i = i+1 # we have consumed a character
-
- if not option:
- self.error("no such option: %s" % opt)
- if option.takes_value():
- # Any characters left in arg? Pretend they're the
- # next arg, and stop consuming characters of arg.
- if i < len(arg):
- rargs.insert(0, arg[i:])
- stop = 1
-
- nargs = option.nargs
- if len(rargs) < nargs:
- if nargs == 1:
- self.error("%s option requires a value" % opt)
- else:
- self.error("%s option requires %s values"
- % (opt, nargs))
- elif nargs == 1:
- value = rargs.pop(0)
- else:
- value = tuple(rargs[0:nargs])
- del rargs[0:nargs]
-
- else: # option doesn't take a value
- value = None
-
- option.process(opt, value, values, self)
-
- if stop:
- break
-
-
- # -- Output/error methods ------------------------------------------
-
- def error (self, msg):
- """error(msg : string)
-
- Print a usage message incorporating 'msg' to stderr and exit.
- If you override this in a subclass, it should not return -- it
- should either exit or raise an exception.
- """
- self.print_usage(sys.stderr)
- sys.stderr.write("\nSCons error: %s\n" % msg)
- sys.exit(2)
-
- def print_usage (self, file=None):
- """print_usage(file : file = stdout)
-
- Print the usage message for the current program (self.usage) to
- 'file' (default stdout). Any occurence of the string "%prog" in
- self.usage is replaced with the name of the current program
- (basename of sys.argv[0]). Does nothing if self.usage is empty
- or not defined.
- """
- if file is None:
- file = sys.stdout
- if self.usage:
- usage = string.replace(self.usage,"%prog", get_prog_name())
- file.write(usage + "\n")
-
- def print_version (self, file=None):
- """print_version(file : file = stdout)
-
- Print the version message for this program (self.version) to
- 'file' (default stdout). As with print_usage(), any occurence
- of "%prog" in self.version is replaced by the current program's
- name. Does nothing if self.version is empty or undefined.
- """
- if file is None:
- file = sys.stdout
- if self.version:
- version = string.replace(self.version,"%prog", get_prog_name())
- file.write(version+"\n")
-
- def print_help (self, file=None):
- """print_help(file : file = stdout)
-
- Print an extended help message, listing all options and any
- help text provided with them, to 'file' (default stdout).
- """
- # SCons: don't import wrap_text from distutils, use the
- # copy we've included below, so we can avoid being dependent
- # on having the right version of distutils installed.
- #from distutils.fancy_getopt import wrap_text
-
- if file is None:
- file = sys.stdout
-
- self.print_usage(file)
-
- # The help for each option consists of two parts:
- # * the opt strings and metavars
- # eg. ("-x", or "-fFILENAME, --file=FILENAME")
- # * the user-supplied help string
- # eg. ("turn on expert mode", "read data from FILENAME")
- #
- # If possible, we write both of these on the same line:
- # -x turn on expert mode
- #
- # But if the opt string list is too long, we put the help
- # string on a second line, indented to the same column it would
- # start in if it fit on the first line.
- # -fFILENAME, --file=FILENAME
- # read data from FILENAME
-
- file.write("Options:\n")
- width = 78 # assume 80 cols for now
-
- option_help = [] # list of (string, string) tuples
- lengths = []
-
- for option in self.option_list:
- takes_value = option.takes_value()
- if takes_value:
- metavar = option.metavar or string.upper(option.dest)
-
- opts = [] # list of "-a" or "--foo=FILE" strings
- if option.help is SUPPRESS_HELP:
- continue
-
- if takes_value:
- for sopt in option._short_opts:
- opts.append(sopt + ' ' + metavar)
- for lopt in option._long_opts:
- opts.append(lopt + "=" + metavar)
- else:
- for opt in option._short_opts + option._long_opts:
- opts.append(opt)
-
- opts = string.join(opts,", ")
- option_help.append((opts, option.help))
- lengths.append(len(opts))
-
- max_opts = min(max(lengths), 26)
-
- for (opts, help) in option_help:
- # how much to indent lines 2 .. N of help text
- indent_rest = 2 + max_opts + 2
- help_width = width - indent_rest
-
- if len(opts) > max_opts:
- opts = " " + opts + "\n"
- indent_first = indent_rest
- else: # start help on same line as opts
- opts = " %-*s " % (max_opts, opts)
- indent_first = 0
-
- file.write(opts)
-
- if help:
- help_lines = wrap_text(help, help_width)
- file.write( "%*s%s\n" % (indent_first, "", help_lines[0]))
- for line in help_lines[1:]:
- file.write(" %*s%s\n" % (indent_rest, "", line))
- elif opts[-1] != "\n":
- file.write("\n")
-
-# class OptionParser
-
-
-def _match_abbrev (s, wordmap):
- """_match_abbrev(s : string, wordmap : {string : Option}) -> string
-
- Return the string key in 'wordmap' for which 's' is an unambiguous
- abbreviation. If 's' is found to be ambiguous or doesn't match any of
- 'words', raise BadOptionError.
- """
- # Is there an exact match?
- if wordmap.has_key(s):
- return s
- else:
- # Isolate all words with s as a prefix.
- possibilities = []
- ls = len(s)
- for word in wordmap.keys():
- if len(word)>=ls and word[:ls]==s:
- possibilities.append(word)
- # No exact match, so there had better be just one possibility.
- if len(possibilities) == 1:
- return possibilities[0]
- elif not possibilities:
- raise BadOptionError("no such option: %s" % s)
- else:
- # More than one possible completion: ambiguous prefix.
- raise BadOptionError("ambiguous option: %s (%s?)"
- % (s, string.join(possibilities,", ")))
-
-# SCons: Include a snarfed copy of wrap_text(), so we're not dependent
-# on the right version of distutils being installed.
-import re
-
-WS_TRANS = string.maketrans(string.whitespace, ' ' * len(string.whitespace))
-
-def wrap_text (text, width):
- """wrap_text(text : string, width : int) -> [string]
-
- Split 'text' into multiple lines of no more than 'width' characters
- each, and return the list of strings that results.
- """
-
- if text is None:
- return []
- if len(text) <= width:
- return [text]
-
- text = string.expandtabs(text)
- text = string.translate(text, WS_TRANS)
- chunks = re.split(r'( +|-+)', text)
- chunks = filter(None, chunks) # ' - ' results in empty strings
- lines = []
-
- while chunks:
-
- cur_line = [] # list of chunks (to-be-joined)
- cur_len = 0 # length of current line
-
- while chunks:
- l = len(chunks[0])
- if cur_len + l <= width: # can squeeze (at least) this chunk in
- cur_line.append(chunks[0])
- del chunks[0]
- cur_len = cur_len + l
- else: # this line is full
- # drop last chunk if all space
- if cur_line and cur_line[-1][0] == ' ':
- del cur_line[-1]
- break
-
- if chunks: # any chunks left to process?
-
- # if the current line is still empty, then we had a single
- # chunk that's too big too fit on a line -- so we break
- # down and break it up at the line width
- if cur_len == 0:
- cur_line.append(chunks[0][0:width])
- chunks[0] = chunks[0][width:]
-
- # all-whitespace chunks at the end of a line can be discarded
- # (and we know from the re.split above that if a chunk has
- # *any* whitespace, it is *all* whitespace)
- if chunks[0][0] == ' ':
- del chunks[0]
-
- # and store this line in the list-of-all-lines -- as a single
- # string, of course!
- lines.append(string.join(cur_line, ''))
-
- # while chunks
-
- return lines
-
-# wrap_text ()
diff --git a/src/engine/SCons/SConfTests.py b/src/engine/SCons/SConfTests.py
index 22ec188..d4ed346 100644
--- a/src/engine/SCons/SConfTests.py
+++ b/src/engine/SCons/SConfTests.py
@@ -163,7 +163,7 @@ class SConfTestCase(unittest.TestCase):
self.name = name
self.state = None
self.waiting_parents = {}
- self.side_effects = {}
+ self.side_effects = []
self.builder = None
def disambiguate(self):
return self
diff --git a/src/engine/SCons/Scanner/ScannerTests.py b/src/engine/SCons/Scanner/ScannerTests.py
index 30dc1df..ae77908 100644
--- a/src/engine/SCons/Scanner/ScannerTests.py
+++ b/src/engine/SCons/Scanner/ScannerTests.py
@@ -240,8 +240,10 @@ class BaseTestCase(unittest.TestCase):
s = SCons.Scanner.Base(self.func, "Hash")
dict = {}
dict[s] = 777
- self.failUnless(hash(dict.keys()[0]) == id(s),
- "did not hash Scanner base class as expected")
+ i = hash(id(s))
+ h = hash(dict.keys()[0])
+ self.failUnless(h == i,
+ "hash Scanner base class expected %s, got %s" % (i, h))
def test_scan_check(self):
"""Test the Scanner.Base class scan_check() method"""
diff --git a/src/engine/SCons/Script/Main.py b/src/engine/SCons/Script/Main.py
index d80050d..53e5129 100644
--- a/src/engine/SCons/Script/Main.py
+++ b/src/engine/SCons/Script/Main.py
@@ -61,7 +61,6 @@ import SCons.Errors
import SCons.Job
import SCons.Node
import SCons.Node.FS
-from SCons.Optik import OptionParser, SUPPRESS_HELP, OptionValueError
import SCons.SConf
import SCons.Script
import SCons.Sig
@@ -70,6 +69,10 @@ import SCons.Util
import SCons.Warnings
#
+
+class SConsPrintHelpException(Exception):
+ pass
+
display = SCons.Util.display
progress_display = SCons.Util.DisplayEngine()
@@ -108,9 +111,9 @@ class BuildTask(SCons.Taskmaster.Task):
def do_failed(self, status=2):
global exit_status
- if ignore_errors:
+ if self.options.ignore_errors:
SCons.Taskmaster.Task.executed(self)
- elif keep_going_on_error:
+ elif self.options.keep_going:
SCons.Taskmaster.Task.fail_continue(self)
exit_status = status
else:
@@ -122,7 +125,7 @@ class BuildTask(SCons.Taskmaster.Task):
if self.top and not t.has_builder() and not t.side_effect:
if not t.exists():
sys.stderr.write("scons: *** Do not know how to make target `%s'." % t)
- if not keep_going_on_error:
+ if not self.options.keep_going:
sys.stderr.write(" Stop.")
sys.stderr.write("\n")
self.do_failed()
@@ -175,7 +178,7 @@ class BuildTask(SCons.Taskmaster.Task):
if e is None:
e = t
s = str(e)
- if t == SCons.Errors.StopError and not keep_going_on_error:
+ if t == SCons.Errors.StopError and not self.options.keep_going:
s = s + ' Stop.'
sys.stderr.write("scons: *** %s\n" % s)
@@ -190,9 +193,9 @@ class BuildTask(SCons.Taskmaster.Task):
def postprocess(self):
if self.top:
t = self.targets[0]
- for tp in tree_printers:
+ for tp in self.options.tree_printers:
tp.display(t)
- if print_includes:
+ if self.options.debug_includes:
tree = t.render_include_tree()
if tree:
print
@@ -202,7 +205,7 @@ class BuildTask(SCons.Taskmaster.Task):
def make_ready(self):
"""Make a task ready for execution"""
SCons.Taskmaster.Task.make_ready(self)
- if self.out_of_date and print_explanations:
+ if self.out_of_date and self.options.debug_explain:
explanation = self.out_of_date[0].explain()
if explanation:
sys.stdout.write("scons: " + explanation)
@@ -316,43 +319,23 @@ class TreePrinter:
# Global variables
-tree_printers = []
-
-keep_going_on_error = 0
-print_explanations = 0
-print_includes = 0
print_objects = 0
print_memoizer = 0
print_stacktrace = 0
print_time = 0
-ignore_errors = 0
sconscript_time = 0
cumulative_command_time = 0
exit_status = 0 # exit status, assume success by default
-repositories = []
num_jobs = None
delayed_warnings = []
-diskcheck_all = SCons.Node.FS.diskcheck_types()
-diskcheck_option_set = None
-
-def diskcheck_convert(value):
- if value is None:
- return []
- if not SCons.Util.is_List(value):
- value = string.split(value, ',')
- result = []
- for v in map(string.lower, value):
- if v == 'all':
- result = diskcheck_all
- elif v == 'none':
- result = []
- elif v in diskcheck_all:
- result.append(v)
- else:
- raise ValueError, v
- return result
+OptionsParser = None
+def AddOption(*args, **kw):
+ if not kw.has_key('default'):
+ kw['default'] = None
+ result = apply(OptionsParser.add_local_option, args, kw)
+ return result
#
class Stats:
def __init__(self):
@@ -482,15 +465,6 @@ def _scons_internal_error():
traceback.print_exc()
sys.exit(2)
-def _varargs(option, parser):
- value = None
- if parser.rargs:
- arg = parser.rargs[0]
- if arg[0] != "-":
- value = arg
- del parser.rargs[0]
- return value
-
def _setup_warn(arg):
"""The --warn option. An argument to this option
should be of the form <warning-class> or no-<warning-class>.
@@ -536,12 +510,11 @@ def _setup_warn(arg):
else:
SCons.Warnings.suppressWarningClass(clazz)
-def _SConstruct_exists(dirname=''):
+def _SConstruct_exists(dirname='', repositories=[]):
"""This function checks that an SConstruct file exists in a directory.
If so, it returns the path of the file. By default, it checks the
current directory.
"""
- global repositories
for file in ['SConstruct', 'Sconstruct', 'sconstruct']:
sfile = os.path.join(dirname, file)
if os.path.isfile(sfile):
@@ -552,49 +525,44 @@ def _SConstruct_exists(dirname=''):
return sfile
return None
-def _set_globals(options):
- global keep_going_on_error, ignore_errors
- global count_stats
- global print_explanations, print_includes, print_memoizer
- global print_objects, print_stacktrace, print_time
- global tree_printers
- global memory_stats
+def _set_debug_values(options):
+ global print_memoizer, print_objects, print_stacktrace, print_time
- keep_going_on_error = options.keep_going
- try:
- debug_values = options.debug
- if debug_values is None:
- debug_values = []
- except AttributeError:
- pass
- else:
- if "count" in debug_values:
+ debug_values = options.debug
+
+ if "count" in debug_values:
+ # All of the object counts are within "if __debug__:" blocks,
+ # which get stripped when running optimized (with python -O or
+ # from compiled *.pyo files). Provide a warning if __debug__ is
+ # stripped, so it doesn't just look like --debug=count is broken.
+ enable_count = False
+ if __debug__: enable_count = True
+ if enable_count:
count_stats.enable(sys.stdout)
- if "dtree" in debug_values:
- tree_printers.append(TreePrinter(derived=True))
- if "explain" in debug_values:
- print_explanations = 1
- if "findlibs" in debug_values:
- SCons.Scanner.Prog.print_find_libs = "findlibs"
- if "includes" in debug_values:
- print_includes = 1
- if "memoizer" in debug_values:
- print_memoizer = 1
- if "memory" in debug_values:
- memory_stats.enable(sys.stdout)
- if "objects" in debug_values:
- print_objects = 1
- if "presub" in debug_values:
- SCons.Action.print_actions_presub = 1
- if "stacktrace" in debug_values:
- print_stacktrace = 1
- if "stree" in debug_values:
- tree_printers.append(TreePrinter(status=True))
- if "time" in debug_values:
- print_time = 1
- if "tree" in debug_values:
- tree_printers.append(TreePrinter())
- ignore_errors = options.ignore_errors
+ else:
+ msg = "--debug=count is not supported when running SCons\n" + \
+ "\twith the python -O option or optimized (.pyo) modules."
+ SCons.Warnings.warn(SCons.Warnings.NoObjectCountWarning, msg)
+ if "dtree" in debug_values:
+ options.tree_printers.append(TreePrinter(derived=True))
+ options.debug_explain = ("explain" in debug_values)
+ if "findlibs" in debug_values:
+ SCons.Scanner.Prog.print_find_libs = "findlibs"
+ options.debug_includes = ("includes" in debug_values)
+ print_memoizer = ("memoizer" in debug_values)
+ if "memory" in debug_values:
+ memory_stats.enable(sys.stdout)
+ print_objects = ("objects" in debug_values)
+ if "presub" in debug_values:
+ SCons.Action.print_actions_presub = 1
+ if "stacktrace" in debug_values:
+ print_stacktrace = 1
+ if "stree" in debug_values:
+ options.tree_printers.append(TreePrinter(status=True))
+ if "time" in debug_values:
+ print_time = 1
+ if "tree" in debug_values:
+ options.tree_printers.append(TreePrinter())
def _create_path(plist):
path = '.'
@@ -655,410 +623,25 @@ def version_string(label, module):
module.__developer__,
module.__buildsys__)
-class OptParser(OptionParser):
- def __init__(self):
- import __main__
-
- parts = ["SCons by Steven Knight et al.:\n"]
- try:
- parts.append(version_string("script", __main__))
- except KeyboardInterrupt:
- raise
- except:
- # On Windows there is no scons.py, so there is no
- # __main__.__version__, hence there is no script version.
- pass
- parts.append(version_string("engine", SCons))
- parts.append("__COPYRIGHT__")
- OptionParser.__init__(self, version=string.join(parts, ''),
- usage="usage: scons [OPTION] [TARGET] ...")
-
- # options ignored for compatibility
- def opt_ignore(option, opt, value, parser):
- sys.stderr.write("Warning: ignoring %s option\n" % opt)
- self.add_option("-b", "-m", "-S", "-t", "--no-keep-going", "--stop",
- "--touch", action="callback", callback=opt_ignore,
- help="Ignored for compatibility.")
-
- self.add_option('-c', '--clean', '--remove', action="store_true",
- dest="clean",
- help="Remove specified targets and dependencies.")
-
- self.add_option('-C', '--directory', type="string", action = "append",
- metavar="DIR",
- help="Change to DIR before doing anything.")
-
- self.add_option('--cache-debug', action="store",
- dest="cache_debug", metavar="FILE",
- help="Print CacheDir debug info to FILE.")
-
- self.add_option('--cache-disable', '--no-cache',
- action="store_true", dest='cache_disable', default=0,
- help="Do not retrieve built targets from CacheDir.")
-
- self.add_option('--cache-force', '--cache-populate',
- action="store_true", dest='cache_force', default=0,
- help="Copy already-built targets into the CacheDir.")
-
- self.add_option('--cache-show',
- action="store_true", dest='cache_show', default=0,
- help="Print build actions for files from CacheDir.")
-
- config_options = ["auto", "force" ,"cache"]
-
- def opt_config(option, opt, value, parser, c_options=config_options):
- if value in c_options:
- parser.values.config = value
- else:
- raise OptionValueError("Warning: %s is not a valid config type" % value)
- self.add_option('--config', action="callback", type="string",
- callback=opt_config, nargs=1, dest="config",
- metavar="MODE", default="auto",
- help="Controls Configure subsystem: "
- "%s." % string.join(config_options, ", "))
-
- def opt_not_yet(option, opt, value, parser):
- sys.stderr.write("Warning: the %s option is not yet implemented\n" % opt)
- sys.exit(0)
- self.add_option('-d', action="callback",
- callback=opt_not_yet,
- help = "Print file dependency information.")
-
- self.add_option('-D', action="store_const", const=2, dest="climb_up",
- help="Search up directory tree for SConstruct, "
- "build all Default() targets.")
-
- debug_options = ["count", "dtree", "explain", "findlibs",
- "includes", "memoizer", "memory", "objects",
- "pdb", "presub", "stacktrace", "stree",
- "time", "tree"]
-
- deprecated_debug_options = {
- "nomemoizer" : ' and has no effect',
- }
-
- def opt_debug(option, opt, value, parser, debug_options=debug_options, deprecated_debug_options=deprecated_debug_options):
- if value in debug_options:
- try:
- if parser.values.debug is None:
- parser.values.debug = []
- except AttributeError:
- parser.values.debug = []
- parser.values.debug.append(value)
- elif value in deprecated_debug_options.keys():
- msg = deprecated_debug_options[value]
- w = "The --debug=%s option is deprecated%s." % (value, msg)
- delayed_warnings.append((SCons.Warnings.DeprecatedWarning, w))
- else:
- raise OptionValueError("Warning: %s is not a valid debug type" % value)
- self.add_option('--debug', action="callback", type="string",
- callback=opt_debug, nargs=1, dest="debug",
- metavar="TYPE",
- help="Print various types of debugging information: "
- "%s." % string.join(debug_options, ", "))
-
- def opt_diskcheck(option, opt, value, parser):
- try:
- global diskcheck_option_set
- diskcheck_option_set = diskcheck_convert(value)
- SCons.Node.FS.set_diskcheck(diskcheck_option_set)
- except ValueError, e:
- raise OptionValueError("Warning: `%s' is not a valid diskcheck type" % e)
-
-
- self.add_option('--diskcheck', action="callback", type="string",
- callback=opt_diskcheck, dest='diskcheck',
- metavar="TYPE",
- help="Enable specific on-disk checks.")
-
- def opt_duplicate(option, opt, value, parser):
- if not value in SCons.Node.FS.Valid_Duplicates:
- raise OptionValueError("`%s' is not a valid duplication style." % value)
- parser.values.duplicate = value
- # Set the duplicate style right away so it can affect linking
- # of SConscript files.
- SCons.Node.FS.set_duplicate(value)
- self.add_option('--duplicate', action="callback", type="string",
- callback=opt_duplicate, nargs=1, dest="duplicate",
- help="Set the preferred duplication methods. Must be one of "
- + string.join(SCons.Node.FS.Valid_Duplicates, ", "))
-
- self.add_option('-f', '--file', '--makefile', '--sconstruct',
- action="append", nargs=1,
- help="Read FILE as the top-level SConstruct file.")
-
- self.add_option('-h', '--help', action="store_true", default=0,
- dest="help",
- help="Print defined help message, or this one.")
-
- self.add_option("-H", "--help-options",
- action="help",
- help="Print this message and exit.")
-
- self.add_option('-i', '--ignore-errors', action="store_true",
- default=0, dest='ignore_errors',
- help="Ignore errors from build actions.")
-
- self.add_option('-I', '--include-dir', action="append",
- dest='include_dir', metavar="DIR",
- help="Search DIR for imported Python modules.")
-
- self.add_option('--implicit-cache', action="store_true",
- dest='implicit_cache',
- help="Cache implicit dependencies")
-
- self.add_option('--implicit-deps-changed', action="store_true",
- default=0, dest='implicit_deps_changed',
- help="Ignore cached implicit dependencies.")
- self.add_option('--implicit-deps-unchanged', action="store_true",
- default=0, dest='implicit_deps_unchanged',
- help="Ignore changes in implicit dependencies.")
-
- def opt_j(option, opt, value, parser):
- value = int(value)
- parser.values.num_jobs = value
- self.add_option('-j', '--jobs', action="callback", type="int",
- callback=opt_j, metavar="N",
- help="Allow N jobs at once.")
-
- self.add_option('-k', '--keep-going', action="store_true", default=0,
- dest='keep_going',
- help="Keep going when a target can't be made.")
-
- self.add_option('--max-drift', type="int", action="store",
- dest='max_drift', metavar="N",
- help="Set maximum system clock drift to N seconds.")
-
- self.add_option('-n', '--no-exec', '--just-print', '--dry-run',
- '--recon', action="store_true", dest='noexec',
- default=0, help="Don't build; just print commands.")
-
- self.add_option('--no-site-dir', action="store_true",
- dest='no_site_dir', default=0,
- help="Don't search or use the usual site_scons dir.")
-
- self.add_option('--profile', action="store",
- dest="profile_file", metavar="FILE",
- help="Profile SCons and put results in FILE.")
-
- self.add_option('-q', '--question', action="store_true", default=0,
- help="Don't build; exit status says if up to date.")
-
- self.add_option('-Q', dest='no_progress', action="store_true",
- default=0,
- help="Suppress \"Reading/Building\" progress messages.")
-
- self.add_option('--random', dest="random", action="store_true",
- default=0, help="Build dependencies in random order.")
-
- self.add_option('-s', '--silent', '--quiet', action="store_true",
- default=0, help="Don't print commands.")
-
- self.add_option('--site-dir', action="store",
- dest='site_dir', metavar="DIR",
- help="Use DIR instead of the usual site_scons dir.")
-
- self.add_option('--taskmastertrace', action="store",
- dest="taskmastertrace_file", metavar="FILE",
- help="Trace Node evaluation to FILE.")
-
- tree_options = ["all", "derived", "prune", "status"]
-
- def opt_tree(option, opt, value, parser, tree_options=tree_options):
- tp = TreePrinter()
- for o in string.split(value, ','):
- if o == 'all':
- tp.derived = False
- elif o == 'derived':
- tp.derived = True
- elif o == 'prune':
- tp.prune = True
- elif o == 'status':
- tp.status = True
- else:
- raise OptionValueError("Warning: %s is not a valid --tree option" % o)
- tree_printers.append(tp)
-
- self.add_option('--tree', action="callback", type="string",
- callback=opt_tree, nargs=1, metavar="OPTIONS",
- help="Print a dependency tree in various formats: "
- "%s." % string.join(tree_options, ", "))
-
- self.add_option('-u', '--up', '--search-up', action="store_const",
- dest="climb_up", default=0, const=1,
- help="Search up directory tree for SConstruct, "
- "build targets at or below current directory.")
- self.add_option('-U', action="store_const", dest="climb_up",
- default=0, const=3,
- help="Search up directory tree for SConstruct, "
- "build Default() targets from local SConscript.")
-
- self.add_option("-v", "--version",
- action="version",
- help="Print the SCons version number and exit.")
-
- self.add_option('--warn', '--warning', nargs=1, action="store",
- metavar="WARNING-SPEC",
- help="Enable or disable warnings.")
-
- self.add_option('-Y', '--repository', '--srcdir',
- nargs=1, action="append",
- help="Search REPOSITORY for source and target files.")
-
- self.add_option('-e', '--environment-overrides', action="callback",
- callback=opt_not_yet,
- # help="Environment variables override makefiles."
- help=SUPPRESS_HELP)
- self.add_option('-l', '--load-average', '--max-load', action="callback",
- callback=opt_not_yet, type="int", dest="load_average",
- # action="store",
- # help="Don't start multiple jobs unless load is below "
- # "LOAD-AVERAGE."
- # type="int",
- help=SUPPRESS_HELP)
- self.add_option('--list-derived', action="callback",
- callback=opt_not_yet,
- # help="Don't build; list files that would be built."
- help=SUPPRESS_HELP)
- self.add_option('--list-actions', action="callback",
- callback=opt_not_yet,
- # help="Don't build; list files and build actions."
- help=SUPPRESS_HELP)
- self.add_option('--list-where', action="callback",
- callback=opt_not_yet,
- # help="Don't build; list files and where defined."
- help=SUPPRESS_HELP)
- self.add_option('-o', '--old-file', '--assume-old', action="callback",
- callback=opt_not_yet, type="string", dest="old_file",
- # help = "Consider FILE to be old; don't rebuild it."
- help=SUPPRESS_HELP)
- self.add_option('--override', action="callback", dest="override",
- callback=opt_not_yet, type="string",
- # help="Override variables as specified in FILE."
- help=SUPPRESS_HELP)
- self.add_option('-p', action="callback",
- callback=opt_not_yet,
- # help="Print internal environments/objects."
- help=SUPPRESS_HELP)
- self.add_option('-r', '-R', '--no-builtin-rules',
- '--no-builtin-variables', action="callback",
- callback=opt_not_yet,
- # help="Clear default environments and variables."
- help=SUPPRESS_HELP)
- self.add_option('-w', '--print-directory', action="callback",
- callback=opt_not_yet,
- # help="Print the current directory."
- help=SUPPRESS_HELP)
- self.add_option('--no-print-directory', action="callback",
- callback=opt_not_yet,
- # help="Turn off -w, even if it was turned on implicitly."
- help=SUPPRESS_HELP)
- self.add_option('--write-filenames', action="callback",
- callback=opt_not_yet, type="string", dest="write_filenames",
- # help="Write all filenames examined into FILE."
- help=SUPPRESS_HELP)
- self.add_option('-W', '--what-if', '--new-file', '--assume-new',
- dest="new_file",
- action="callback", callback=opt_not_yet, type="string",
- # help="Consider FILE to be changed."
- help=SUPPRESS_HELP)
- self.add_option('--warn-undefined-variables', action="callback",
- callback=opt_not_yet,
- # help="Warn when an undefined variable is referenced."
- help=SUPPRESS_HELP)
-
- def parse_args(self, args=None, values=None):
- opt, arglist = OptionParser.parse_args(self, args, values)
- if opt.implicit_deps_changed or opt.implicit_deps_unchanged:
- opt.implicit_cache = 1
- return opt, arglist
-
-class SConscriptSettableOptions:
- """This class wraps an OptParser instance and provides
- uniform access to options that can be either set on the command
- line or from a SConscript file. A value specified on the command
- line always overrides a value set in a SConscript file.
- Not all command line options are SConscript settable, and the ones
- that are must be explicitly added to settable dictionary and optionally
- validated and coerced in the set() method."""
-
- def __init__(self, options):
- self.options = options
-
- # This dictionary stores the defaults for all the SConscript
- # settable options, as well as indicating which options
- # are SConscript settable (and gettable, which for options
- # like 'help' is far more important than being settable).
- self.settable = {
- 'clean' : 0,
- 'diskcheck' : diskcheck_all,
- 'duplicate' : 'hard-soft-copy',
- 'help' : 0,
- 'implicit_cache' : 0,
- 'max_drift' : SCons.Node.FS.default_max_drift,
- 'num_jobs' : 1,
- 'random' : 0,
- }
-
- def get(self, name):
- if not self.settable.has_key(name):
- raise SCons.Errors.UserError, "This option is not settable from a SConscript file: %s"%name
- if hasattr(self.options, name) and getattr(self.options, name) is not None:
- return getattr(self.options, name)
- else:
- return self.settable[name]
-
- def set(self, name, value):
- if not self.settable.has_key(name):
- raise SCons.Errors.UserError, "This option is not settable from a SConscript file: %s"%name
-
- if name == 'num_jobs':
- try:
- value = int(value)
- if value < 1:
- raise ValueError
- except ValueError:
- raise SCons.Errors.UserError, "A positive integer is required: %s"%repr(value)
- elif name == 'max_drift':
- try:
- value = int(value)
- except ValueError:
- raise SCons.Errors.UserError, "An integer is required: %s"%repr(value)
- elif name == 'duplicate':
- try:
- value = str(value)
- except ValueError:
- raise SCons.Errors.UserError, "A string is required: %s"%repr(value)
- if not value in SCons.Node.FS.Valid_Duplicates:
- raise SCons.Errors.UserError, "Not a valid duplication style: %s" % value
- # Set the duplicate stye right away so it can affect linking
- # of SConscript files.
- SCons.Node.FS.set_duplicate(value)
- elif name == 'diskcheck':
- try:
- value = diskcheck_convert(value)
- except ValueError, v:
- raise SCons.Errors.UserError, "Not a valid diskcheck value: %s"%v
- if not diskcheck_option_set:
- SCons.Node.FS.set_diskcheck(value)
-
- self.settable[name] = value
-
-
-def _main(args, parser):
+def _main(parser):
global exit_status
+ options = parser.values
+
# Here's where everything really happens.
- # First order of business: set up default warnings and and then
- # handle the user's warning options, so we can warn about anything
- # that happens appropriately.
+ # First order of business: set up default warnings and then
+ # handle the user's warning options, so that we can issue (or
+ # suppress) appropriate warnings about anything that might happen,
+ # as configured by the user.
+
default_warnings = [ SCons.Warnings.CorruptSConsignWarning,
SCons.Warnings.DeprecatedWarning,
SCons.Warnings.DuplicateEnvironmentWarning,
SCons.Warnings.MissingSConscriptWarning,
SCons.Warnings.NoMD5ModuleWarning,
SCons.Warnings.NoMetaclassSupportWarning,
+ SCons.Warnings.NoObjectCountWarning,
SCons.Warnings.NoParallelSupportWarning,
SCons.Warnings.MisleadingKeywordsWarning, ]
for warning in default_warnings:
@@ -1067,9 +650,21 @@ def _main(args, parser):
if options.warn:
_setup_warn(options.warn)
+ # Now that we have the warnings configuration set up, we can actually
+ # issue (or suppress) any warnings about warning-worthy things that
+ # occurred while the command-line options were getting parsed.
+ try:
+ dw = options.delayed_warnings
+ except AttributeError:
+ pass
+ else:
+ delayed_warnings.extend(dw)
for warning_type, message in delayed_warnings:
SCons.Warnings.warn(warning_type, message)
+ if options.diskcheck:
+ SCons.Node.FS.set_diskcheck(options.diskcheck)
+
# Next, we want to create the FS object that represents the outside
# world's file system, as that's central to a lot of initialization.
# To do this, however, we need to be in the directory from which we
@@ -1082,17 +677,11 @@ def _main(args, parser):
except OSError:
sys.stderr.write("Could not change directory to %s\n" % cdir)
- # The SConstruct file may be in a repository, so initialize those
- # before we start the search up our path for one.
- global repositories
- if options.repository:
- repositories.extend(options.repository)
-
target_top = None
if options.climb_up:
target_top = '.' # directory to prepend to targets
script_dir = os.getcwd() # location of script
- while script_dir and not _SConstruct_exists(script_dir):
+ while script_dir and not _SConstruct_exists(script_dir, options.repository):
script_dir, last_part = os.path.split(script_dir)
if last_part:
target_top = os.path.join(last_part, target_top)
@@ -1107,7 +696,7 @@ def _main(args, parser):
# and make it the build engine default.
fs = SCons.Node.FS.default_fs = SCons.Node.FS.FS()
- for rep in repositories:
+ for rep in options.repository:
fs.Repository(rep)
# Now that we have the FS object, the next order of business is to
@@ -1117,7 +706,7 @@ def _main(args, parser):
if options.file:
scripts.extend(options.file)
if not scripts:
- sfile = _SConstruct_exists()
+ sfile = _SConstruct_exists(repositories=options.repository)
if sfile:
scripts.append(sfile)
@@ -1126,9 +715,7 @@ def _main(args, parser):
# There's no SConstruct, but they specified -h.
# Give them the options usage now, before we fail
# trying to read a non-existent SConstruct file.
- parser.print_help()
- exit_status = 0
- return
+ raise SConsPrintHelpException
raise SCons.Errors.UserError, "No SConstruct file found."
if scripts[0] == "-":
@@ -1137,16 +724,11 @@ def _main(args, parser):
d = fs.File(scripts[0]).dir
fs.set_SConstruct_dir(d)
- # Now that we have the FS object and it's intialized, set up (most
- # of) the rest of the options.
- global ssoptions
- ssoptions = SConscriptSettableOptions(options)
-
- _set_globals(options)
+ _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
- if options.noexec:
+ if options.no_exec:
SCons.SConf.dryrun = 1
SCons.Action.execute_actions = None
CleanTask.execute = CleanTask.show
@@ -1185,12 +767,14 @@ def _main(args, parser):
# read and execute have access to them.
targets = []
xmit_args = []
- for a in args:
+ for a in parser.largs:
+ if a[0] == '-':
+ continue
if '=' in a:
xmit_args.append(a)
else:
targets.append(a)
- SCons.Script._Add_Targets(targets)
+ SCons.Script._Add_Targets(targets + parser.rargs)
SCons.Script._Add_Arguments(xmit_args)
sys.stdout = SCons.Util.Unbuffered(sys.stdout)
@@ -1198,6 +782,8 @@ def _main(args, parser):
memory_stats.append('before reading SConscript files:')
count_stats.append(('pre-', 'read'))
+ # And here's where we (finally) read the SConscript files.
+
progress_display("scons: Reading SConscript files ...")
start_time = time.time()
@@ -1215,37 +801,50 @@ def _main(args, parser):
sys.exit(exit_status)
global sconscript_time
sconscript_time = time.time() - start_time
- SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
- progress_display("scons: done reading SConscript files.")
- # Tell the Node.FS subsystem that we're all done reading the
- # SConscript files and calling Repository() and BuildDir() and the
- # like, so it can go ahead and start memoizing the string values of
- # file system nodes.
- SCons.Node.FS.save_strings(1)
+ progress_display("scons: done reading SConscript files.")
memory_stats.append('after reading SConscript files:')
count_stats.append(('post-', 'read'))
- fs.chdir(fs.Top)
+ SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
+
+ # Now re-parse the command-line options (any to the left of a '--'
+ # argument, that is) with any user-defined command-line options that
+ # the SConscript files may have added to the parser object. This will
+ # emit the appropriate error message and exit if any unknown option
+ # was specified on the command line.
+
+ parser.preserve_unknown_options = False
+ parser.parse_args(parser.largs, options)
- if ssoptions.get('help'):
+ if options.help:
help_text = SCons.Script.help_text
if help_text is None:
# They specified -h, but there was no Help() inside the
# SConscript files. Give them the options usage.
- parser.print_help(sys.stdout)
+ raise SConsPrintHelpException
else:
print help_text
print "Use scons -H for help about command-line options."
exit_status = 0
return
+ # Change directory to the top-level SConstruct directory, then tell
+ # the Node.FS subsystem that we're all done reading the SConscript
+ # files and calling Repository() and BuildDir() and changing
+ # directories and the like, so it can go ahead and start memoizing
+ # the string values of file system nodes.
+
+ fs.chdir(fs.Top)
+
+ SCons.Node.FS.save_strings(1)
+
# Now that we've read the SConscripts we can set the options
# that are SConscript settable:
- SCons.Node.implicit_cache = ssoptions.get('implicit_cache')
- SCons.Node.FS.set_duplicate(ssoptions.get('duplicate'))
- fs.set_max_drift(ssoptions.get('max_drift'))
+ SCons.Node.implicit_cache = options.implicit_cache
+ SCons.Node.FS.set_duplicate(options.duplicate)
+ fs.set_max_drift(options.max_drift)
lookup_top = None
if targets or SCons.Script.BUILD_TARGETS != SCons.Script._build_plus_default:
@@ -1324,18 +923,18 @@ def _main(args, parser):
task_class = BuildTask # default action is to build targets
opening_message = "Building targets ..."
closing_message = "done building targets."
- if keep_going_on_error:
+ if options.keep_going:
failure_message = "done building targets (errors occurred during build)."
else:
failure_message = "building terminated because of errors."
if options.question:
task_class = QuestionTask
try:
- if ssoptions.get('clean'):
+ if options.clean:
task_class = CleanTask
opening_message = "Cleaning targets ..."
closing_message = "done cleaning targets."
- if keep_going_on_error:
+ if options.keep_going:
closing_message = "done cleaning targets (errors occurred during clean)."
else:
failure_message = "cleaning terminated because of errors."
@@ -1367,8 +966,12 @@ def _main(args, parser):
tmtrace = None
taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace)
+ # Let the BuildTask objects get at the options to respond to the
+ # various print_* settings, tree_printer list, etc.
+ BuildTask.options = options
+
global num_jobs
- num_jobs = ssoptions.get('num_jobs')
+ num_jobs = options.num_jobs
jobs = SCons.Job.Jobs(num_jobs, taskmaster)
if num_jobs > 1 and jobs.num_jobs == 1:
msg = "parallel builds are unsupported by this version of Python;\n" + \
@@ -1381,26 +984,26 @@ def _main(args, parser):
try:
jobs.run()
finally:
+ jobs.cleanup()
if exit_status:
progress_display("scons: " + failure_message)
else:
progress_display("scons: " + closing_message)
- if not options.noexec:
+ if not options.no_exec:
SCons.SConsign.write()
memory_stats.append('after building targets:')
count_stats.append(('post-', 'build'))
-def _exec_main():
+def _exec_main(parser, values):
sconsflags = os.environ.get('SCONSFLAGS', '')
all_args = string.split(sconsflags) + sys.argv[1:]
- parser = OptParser()
- global options
- options, args = parser.parse_args(all_args)
+ options, args = parser.parse_args(all_args, values)
+
if type(options.debug) == type([]) and "pdb" in options.debug:
import pdb
- pdb.Pdb().runcall(_main, args, parser)
+ pdb.Pdb().runcall(_main, parser)
elif options.profile_file:
from profile import Profile
@@ -1418,19 +1021,42 @@ def _exec_main():
prof = Profile()
try:
- prof.runcall(_main, args, parser)
+ prof.runcall(_main, parser)
+ except SConsPrintHelpException, e:
+ prof.dump_stats(options.profile_file)
+ raise e
except SystemExit:
pass
prof.dump_stats(options.profile_file)
else:
- _main(args, parser)
+ _main(parser)
def main():
+ global OptionsParser
global exit_status
global first_command_start
+
+ parts = ["SCons by Steven Knight et al.:\n"]
+ try:
+ parts.append(version_string("script", __main__))
+ except KeyboardInterrupt:
+ raise
+ except:
+ # On Windows there is no scons.py, so there is no
+ # __main__.__version__, hence there is no script version.
+ pass
+ parts.append(version_string("engine", SCons))
+ parts.append("__COPYRIGHT__")
+ version = string.join(parts, '')
+
+ import SConsOptions
+ parser = SConsOptions.Parser(version)
+ values = SConsOptions.SConsValues(parser.get_default_values())
+
+ OptionsParser = parser
try:
- _exec_main()
+ _exec_main(parser, values)
except SystemExit, s:
if s:
exit_status = s
@@ -1443,6 +1069,9 @@ def main():
_scons_internal_error()
except SCons.Errors.UserError, e:
_scons_user_error(e)
+ except SConsPrintHelpException:
+ parser.print_help()
+ exit_status = 0
except:
# An exception here is likely a builtin Python exception Python
# code in an SConscript file. Show them precisely what the
@@ -1472,7 +1101,10 @@ def main():
if num_jobs == 1:
ct = cumulative_command_time
else:
- ct = last_command_end - first_command_start
+ if last_command_end is None or first_command_start is None:
+ ct = 0.0
+ else:
+ ct = last_command_end - first_command_start
scons_time = total_time - sconscript_time - ct
print "Total build time: %f seconds"%total_time
print "Total SConscript file execution time: %f seconds"%sconscript_time
diff --git a/src/engine/SCons/Script/MainTests.py b/src/engine/SCons/Script/MainTests.py
index aaf1482..fe32df9 100644
--- a/src/engine/SCons/Script/MainTests.py
+++ b/src/engine/SCons/Script/MainTests.py
@@ -37,37 +37,9 @@ import SCons.Script.Main
# have to reach into SCons.Script.Main for various classes or other bits
# of private functionality.
-class SConscriptSettableOptionsTestCase(unittest.TestCase):
-
- def setUp(self):
- class fake_option:
- pass
- option = fake_option()
- self.ssoptions = SCons.Script.Main.SConscriptSettableOptions(option)
-
- def test_get(self):
- """Test trying to get an option that is not SConscript-gettable"""
- try:
- self.ssoptions.get('memoizer')
- except SCons.Errors.UserError:
- pass
- else:
- raise ValueError, "expected a UserError trying to get('memoizer')"
-
- def test_set(self):
- """Test trying to set an option that is not SConscript-settable"""
- try:
- self.ssoptions.set('count', 1)
- except SCons.Errors.UserError:
- pass
- else:
- raise ValueError, "expected a UserError trying to set('count', 1)"
-
-
-
if __name__ == "__main__":
suite = unittest.TestSuite()
- tclasses = [ SConscriptSettableOptionsTestCase, ]
+ tclasses = []
for tclass in tclasses:
names = unittest.getTestCaseNames(tclass, 'test_')
suite.addTests(map(tclass, names))
diff --git a/src/engine/SCons/Script/SConsOptions.py b/src/engine/SCons/Script/SConsOptions.py
new file mode 100644
index 0000000..053fff1
--- /dev/null
+++ b/src/engine/SCons/Script/SConsOptions.py
@@ -0,0 +1,819 @@
+#
+# __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__"
+
+import SCons.compat
+
+import optparse
+import string
+import sys
+import textwrap
+
+try:
+ from gettext import gettext
+except ImportError:
+ def gettext(message):
+ return message
+_ = gettext
+
+import SCons.Node.FS
+
+OptionValueError = optparse.OptionValueError
+SUPPRESS_HELP = optparse.SUPPRESS_HELP
+
+diskcheck_all = SCons.Node.FS.diskcheck_types()
+
+def diskcheck_convert(value):
+ if value is None:
+ return []
+ if not SCons.Util.is_List(value):
+ value = string.split(value, ',')
+ result = []
+ for v in map(string.lower, value):
+ if v == 'all':
+ result = diskcheck_all
+ elif v == 'none':
+ result = []
+ elif v in diskcheck_all:
+ result.append(v)
+ else:
+ raise ValueError, v
+ return result
+
+class SConsValues(optparse.Values):
+ """
+ Holder class for uniform access to SCons options, regardless
+ of whether or not they can be set on the command line or in the
+ SConscript files (using the SetOption() function).
+
+ A SCons option value can originate three different ways:
+
+ 1) set on the command line;
+ 2) set in an SConscript file;
+ 3) the default setting (from the the op.add_option()
+ calls in the Parser() function, below).
+
+ The command line always overrides a value set in a SConscript file,
+ which in turn always overrides default settings. Because we want
+ to support user-specified options in the SConscript file itself,
+ though, we may not know about all of the options when the command
+ line is first parsed, so we can't make all the necessary precedence
+ decisions at the time the option is configured.
+
+ The solution implemented in this class is to keep these different sets
+ of settings separate (command line, SConscript file, and default)
+ and to override the __getattr__() method to check them in turn.
+ This should allow the rest of the code to just fetch values as
+ attributes of an instance of this class, without having to worry
+ about where they came from.
+
+ Note that not all command line options are settable from SConscript
+ files, and the ones that are must be explicitly added to the
+ "settable" list in this class, and optionally validated and coerced
+ in the set_option() method.
+ """
+
+ def __init__(self, defaults):
+ self.__dict__['__defaults__'] = defaults
+ self.__dict__['__SConscript_settings__'] = {}
+
+ def __getattr__(self, attr):
+ """
+ Fetches an options value, checking first for explicit settings
+ from the command line (which are direct attributes), then the
+ SConscript file settings, then the default values.
+ """
+ try:
+ return self.__dict__[attr]
+ except KeyError:
+ try:
+ return self.__dict__['__SConscript_settings__'][attr]
+ except KeyError:
+ return getattr(self.__dict__['__defaults__'], attr)
+
+ settable = [
+ 'clean',
+ 'diskcheck',
+ 'duplicate',
+ 'help',
+ 'implicit_cache',
+ 'max_drift',
+ 'no_exec',
+ 'num_jobs',
+ 'random',
+ ]
+
+ def set_option(self, name, value):
+ """
+ Sets an option from an SConscript file.
+ """
+ if not name in self.settable:
+ raise SCons.Errors.UserError, "This option is not settable from a SConscript file: %s"%name
+
+ if name == 'num_jobs':
+ try:
+ value = int(value)
+ if value < 1:
+ raise ValueError
+ except ValueError:
+ raise SCons.Errors.UserError, "A positive integer is required: %s"%repr(value)
+ elif name == 'max_drift':
+ try:
+ value = int(value)
+ except ValueError:
+ raise SCons.Errors.UserError, "An integer is required: %s"%repr(value)
+ elif name == 'duplicate':
+ try:
+ value = str(value)
+ except ValueError:
+ raise SCons.Errors.UserError, "A string is required: %s"%repr(value)
+ if not value in SCons.Node.FS.Valid_Duplicates:
+ raise SCons.Errors.UserError, "Not a valid duplication style: %s" % value
+ # Set the duplicate style right away so it can affect linking
+ # of SConscript files.
+ SCons.Node.FS.set_duplicate(value)
+ elif name == 'diskcheck':
+ try:
+ value = diskcheck_convert(value)
+ except ValueError, v:
+ raise SCons.Errors.UserError, "Not a valid diskcheck value: %s"%v
+ if not self.__dict__.has_key('diskcheck'):
+ # No --diskcheck= option was specified on the command line.
+ # 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)
+
+ self.__SConscript_settings__[name] = value
+
+class SConsOptionGroup(optparse.OptionGroup):
+ """
+ A subclass for SCons-specific option groups.
+
+ The only difference between this and the base class is that we print
+ the group's help text flush left, underneath their own title but
+ lined up with the normal "SCons Options".
+ """
+ def format_help(self, formatter):
+ """
+ Format an option group's help text, outdenting the title so it's
+ flush with the "SCons Options" title we print at the top.
+ """
+ formatter.dedent()
+ result = formatter.format_heading(self.title)
+ formatter.indent()
+ result = result + optparse.OptionContainer.format_help(self, formatter)
+ return result
+
+class SConsOptionParser(optparse.OptionParser):
+ preserve_unknown_options = False
+
+ def error(self, msg):
+ self.print_usage(sys.stderr)
+ sys.stderr.write("SCons error: %s\n" % msg)
+ sys.exit(2)
+
+ def _process_long_opt(self, rargs, values):
+ """
+ SCons-specific processing of long options.
+
+ This is copied directly from the normal
+ optparse._process_long_opt() method, except that, if configured
+ to do so, we catch the exception thrown when an unknown option
+ is encountered and just stick it back on the "leftover" arguments
+ for later (re-)processing.
+ """
+ arg = rargs.pop(0)
+
+ # Value explicitly attached to arg? Pretend it's the next
+ # argument.
+ if "=" in arg:
+ (opt, next_arg) = string.split(arg, "=", 1)
+ rargs.insert(0, next_arg)
+ had_explicit_value = True
+ else:
+ opt = arg
+ had_explicit_value = False
+
+ try:
+ opt = self._match_long_opt(opt)
+ except optparse.BadOptionError:
+ if self.preserve_unknown_options:
+ # SCons-specific: if requested, add unknown options to
+ # the "leftover arguments" list for later processing.
+ self.largs.append(arg)
+ if had_explicit_value:
+ # The unknown option will be re-processed later,
+ # so undo the insertion of the explicit value.
+ rargs.pop(0)
+ return
+ raise
+
+ option = self._long_opt[opt]
+ if option.takes_value():
+ nargs = option.nargs
+ if len(rargs) < nargs:
+ if nargs == 1:
+ self.error(_("%s option requires an argument") % opt)
+ else:
+ self.error(_("%s option requires %d arguments")
+ % (opt, nargs))
+ elif nargs == 1:
+ value = rargs.pop(0)
+ else:
+ value = tuple(rargs[0:nargs])
+ del rargs[0:nargs]
+
+ elif had_explicit_value:
+ self.error(_("%s option does not take a value") % opt)
+
+ else:
+ value = None
+
+ option.process(opt, value, values, self)
+
+ def add_local_option(self, *args, **kw):
+ """
+ Adds a local option to the parser.
+
+ This is initiated by a SetOption() call to add a user-defined
+ command-line option. We add the option to a separate option
+ group for the local options, creating the group if necessary.
+ """
+ try:
+ group = self.local_option_group
+ except AttributeError:
+ group = SConsOptionGroup(self, 'Local Options')
+ group = self.add_option_group(group)
+ self.local_option_group = group
+
+ result = apply(group.add_option, args, kw)
+
+ if result:
+ # The option was added succesfully. We now have to add the
+ # default value to our object that holds the default values
+ # (so that an attempt to fetch the option's attribute will
+ # yield the default value when not overridden) and then
+ # we re-parse the leftover command-line options, so that
+ # any value overridden on the command line is immediately
+ # available if the user turns around and does a GetOption()
+ # right away.
+ setattr(self.values.__defaults__, result.dest, result.default)
+ self.parse_args(self.largs, self.values)
+
+ return result
+
+class SConsIndentedHelpFormatter(optparse.IndentedHelpFormatter):
+ def format_usage(self, usage):
+ return "usage: %s\n" % usage
+
+ def format_heading(self, heading):
+ """
+ This translates any heading of "options" or "Options" into
+ "SCons Options." Unfortunately, we have to do this here,
+ because those titles are hard-coded in the optparse calls.
+ """
+ if heading == 'options':
+ # The versions of optparse.py shipped with Pythons 2.3 and
+ # 2.4 pass this in uncapitalized; override that so we get
+ # consistent output on all versions.
+ heading = "Options"
+ if heading == 'Options':
+ heading = "SCons Options"
+ return optparse.IndentedHelpFormatter.format_heading(self, heading)
+
+ def format_option(self, option):
+ """
+ A copy of the normal optparse.IndentedHelpFormatter.format_option()
+ method, snarfed so we can set the subsequent_indent on the
+ textwrap.wrap() call below...
+ """
+ # The help for each option consists of two parts:
+ # * the opt strings and metavars
+ # eg. ("-x", or "-fFILENAME, --file=FILENAME")
+ # * the user-supplied help string
+ # eg. ("turn on expert mode", "read data from FILENAME")
+ #
+ # If possible, we write both of these on the same line:
+ # -x turn on expert mode
+ #
+ # But if the opt string list is too long, we put the help
+ # string on a second line, indented to the same column it would
+ # start in if it fit on the first line.
+ # -fFILENAME, --file=FILENAME
+ # read data from FILENAME
+ result = []
+
+ try:
+ opts = self.option_strings[option]
+ except AttributeError:
+ # The Python 2.3 version of optparse attaches this to
+ # to the option argument, not to this object.
+ opts = option.option_strings
+
+ opt_width = self.help_position - self.current_indent - 2
+ if len(opts) > opt_width:
+ opts = "%*s%s\n" % (self.current_indent, "", opts)
+ indent_first = self.help_position
+ else: # start help on same line as opts
+ opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts)
+ indent_first = 0
+ result.append(opts)
+ if option.help:
+
+ try:
+ expand_default = self.expand_default
+ except AttributeError:
+ # The HelpFormatter base class in the Python 2.3 version
+ # of optparse has no expand_default() method.
+ help_text = option.help
+ else:
+ help_text = expand_default(option)
+
+ # SCons: indent every line of the help text but the first.
+ help_lines = textwrap.wrap(help_text, self.help_width,
+ subsequent_indent = ' ')
+ result.append("%*s%s\n" % (indent_first, "", help_lines[0]))
+ for line in help_lines[1:]:
+ result.append("%*s%s\n" % (self.help_position, "", line))
+ elif opts[-1] != "\n":
+ result.append("\n")
+ return string.join(result, "")
+
+ # For consistent help output across Python versions, we provide a
+ # subclass copy of format_option_strings() and these two variables.
+ # This is necessary (?) for Python2.3, which otherwise concatenates
+ # a short option with its metavar.
+ _short_opt_fmt = "%s %s"
+ _long_opt_fmt = "%s=%s"
+
+ def format_option_strings(self, option):
+ """Return a comma-separated list of option strings & metavariables."""
+ if option.takes_value():
+ metavar = option.metavar or string.upper(option.dest)
+ short_opts = []
+ for sopt in option._short_opts:
+ short_opts.append(self._short_opt_fmt % (sopt, metavar))
+ long_opts = []
+ for lopt in option._long_opts:
+ long_opts.append(self._long_opt_fmt % (lopt, metavar))
+ else:
+ short_opts = option._short_opts
+ long_opts = option._long_opts
+
+ if self.short_first:
+ opts = short_opts + long_opts
+ else:
+ opts = long_opts + short_opts
+
+ return string.join(opts, ", ")
+
+def Parser(version):
+ """
+ Returns an options parser object initialized with the standard
+ SCons options.
+ """
+
+ formatter = SConsIndentedHelpFormatter(max_help_position=30)
+
+ op = SConsOptionParser(add_help_option=False,
+ formatter=formatter,
+ usage="usage: scons [OPTION] [TARGET] ...",)
+
+ op.preserve_unknown_options = True
+
+ # Add the options to the parser we just created.
+ #
+ # These are in the order we want them to show up in the -H help
+ # text, basically alphabetical. Each op.add_option() call below
+ # should have a consistent format:
+ #
+ # op.add_option("-L", "--long-option-name",
+ # nargs=1, type="string",
+ # dest="long_option_name", default='foo',
+ # action="callback", callback=opt_long_option,
+ # help="help text goes here",
+ # metavar="VAR")
+ #
+ # Even though the optparse module constructs reasonable default
+ # destination names from the long option names, we're going to be
+ # explicit about each one for easier readability and so this code
+ # will at least show up when grepping the source for option attribute
+ # names, or otherwise browsing the source code.
+
+ # options ignored for compatibility
+ def opt_ignore(option, opt, value, parser):
+ sys.stderr.write("Warning: ignoring %s option\n" % opt)
+ op.add_option("-b", "-m", "-S", "-t",
+ "--no-keep-going", "--stop", "--touch",
+ action="callback", callback=opt_ignore,
+ help="Ignored for compatibility.")
+
+ op.add_option('-c', '--clean', '--remove',
+ dest="clean", default=False,
+ action="store_true",
+ help="Remove specified targets and dependencies.")
+
+ op.add_option('-C', '--directory',
+ nargs=1, type="string",
+ dest="directory", default=[],
+ action="append",
+ help="Change to DIR before doing anything.",
+ metavar="DIR")
+
+ op.add_option('--cache-debug',
+ nargs=1,
+ dest="cache_debug", default=None,
+ action="store",
+ help="Print CacheDir debug info to FILE.",
+ metavar="FILE")
+
+ op.add_option('--cache-disable', '--no-cache',
+ dest='cache_disable', default=False,
+ action="store_true",
+ help="Do not retrieve built targets from CacheDir.")
+
+ op.add_option('--cache-force', '--cache-populate',
+ dest='cache_force', default=False,
+ action="store_true",
+ help="Copy already-built targets into the CacheDir.")
+
+ op.add_option('--cache-show',
+ dest='cache_show', default=False,
+ action="store_true",
+ help="Print build actions for files from CacheDir.")
+
+ config_options = ["auto", "force" ,"cache"]
+
+ def opt_config(option, opt, value, parser, c_options=config_options):
+ if not value in c_options:
+ raise OptionValueError("Warning: %s is not a valid config type" % value)
+ setattr(parser.values, option.dest, value)
+ opt_config_help = "Controls Configure subsystem: %s." \
+ % string.join(config_options, ", ")
+ op.add_option('--config',
+ nargs=1, type="string",
+ dest="config", default="auto",
+ action="callback", callback=opt_config,
+ help = opt_config_help,
+ metavar="MODE")
+
+ def opt_not_yet(option, opt, value, parser):
+ sys.stderr.write("Warning: the %s option is not yet implemented\n" % opt)
+ sys.exit(0)
+ op.add_option('-d',
+ action="callback", callback=opt_not_yet,
+ help = "Print file dependency information.")
+
+ op.add_option('-D',
+ dest="climb_up", default=None,
+ action="store_const", const=2,
+ help="Search up directory tree for SConstruct, "
+ "build all Default() targets.")
+
+ debug_options = ["count", "dtree", "explain", "findlibs",
+ "includes", "memoizer", "memory", "objects",
+ "pdb", "presub", "stacktrace", "stree",
+ "time", "tree"]
+
+ deprecated_debug_options = {
+ "nomemoizer" : ' and has no effect',
+ }
+
+ def opt_debug(option, opt, value, parser,
+ debug_options=debug_options,
+ deprecated_debug_options=deprecated_debug_options):
+ if value in debug_options:
+ parser.values.debug.append(value)
+ elif value in deprecated_debug_options.keys():
+ try:
+ parser.values.delayed_warnings
+ except AttributeError:
+ parser.values.delayed_warnings = []
+ msg = deprecated_debug_options[value]
+ w = "The --debug=%s option is deprecated%s." % (value, msg)
+ t = (SCons.Warnings.DeprecatedWarning, w)
+ parser.values.delayed_warnings.append(t)
+ else:
+ raise OptionValueError("Warning: %s is not a valid debug type" % value)
+ opt_debug_help = "Print various types of debugging information: %s." \
+ % string.join(debug_options, ", ")
+ op.add_option('--debug',
+ nargs=1, type="string",
+ dest="debug", default=[],
+ action="callback", callback=opt_debug,
+ help=opt_debug_help,
+ metavar="TYPE")
+
+ def opt_diskcheck(option, opt, value, parser):
+ try:
+ diskcheck_value = diskcheck_convert(value)
+ except ValueError, e:
+ raise OptionValueError("Warning: `%s' is not a valid diskcheck type" % e)
+ setattr(parser.values, option.dest, diskcheck_value)
+
+ op.add_option('--diskcheck',
+ nargs=1, type="string",
+ dest='diskcheck', default=None,
+ action="callback", callback=opt_diskcheck,
+ help="Enable specific on-disk checks.",
+ metavar="TYPE")
+
+ def opt_duplicate(option, opt, value, parser):
+ if not value in SCons.Node.FS.Valid_Duplicates:
+ raise OptionValueError("`%s' is not a valid duplication style." % value)
+ setattr(parser.values, option.dest, value)
+ # Set the duplicate style right away so it can affect linking
+ # of SConscript files.
+ SCons.Node.FS.set_duplicate(value)
+
+ opt_duplicate_help = "Set the preferred duplication methods. Must be one of " \
+ + string.join(SCons.Node.FS.Valid_Duplicates, ", ")
+
+ op.add_option('--duplicate',
+ nargs=1, type="string",
+ dest="duplicate", default='hard-soft-copy',
+ action="callback", callback=opt_duplicate,
+ help=opt_duplicate_help)
+
+ op.add_option('-f', '--file', '--makefile', '--sconstruct',
+ nargs=1, type="string",
+ dest="file", default=[],
+ action="append",
+ help="Read FILE as the top-level SConstruct file.")
+
+ op.add_option('-h', '--help',
+ dest="help", default=False,
+ action="store_true",
+ help="Print defined help message, or this one.")
+
+ op.add_option("-H", "--help-options",
+ action="help",
+ help="Print this message and exit.")
+
+ op.add_option('-i', '--ignore-errors',
+ dest='ignore_errors', default=False,
+ action="store_true",
+ help="Ignore errors from build actions.")
+
+ op.add_option('-I', '--include-dir',
+ nargs=1,
+ dest='include_dir', default=[],
+ action="append",
+ help="Search DIR for imported Python modules.",
+ metavar="DIR")
+
+ op.add_option('--implicit-cache',
+ dest='implicit_cache', default=False,
+ action="store_true",
+ help="Cache implicit dependencies")
+
+ def opt_implicit_deps(option, opt, value, parser):
+ setattr(parser.values, 'implicit_cache', True)
+ setattr(parser.values, option.dest, True)
+
+ op.add_option('--implicit-deps-changed',
+ dest="implicit_deps_changed", default=False,
+ action="callback", callback=opt_implicit_deps,
+ help="Ignore cached implicit dependencies.")
+
+ op.add_option('--implicit-deps-unchanged',
+ dest="implicit_deps_unchanged", default=False,
+ action="callback", callback=opt_implicit_deps,
+ help="Ignore changes in implicit dependencies.")
+
+ op.add_option('-j', '--jobs',
+ nargs=1, type="int",
+ dest="num_jobs", default=1,
+ action="store",
+ help="Allow N jobs at once.",
+ metavar="N")
+
+ op.add_option('-k', '--keep-going',
+ dest='keep_going', default=False,
+ action="store_true",
+ help="Keep going when a target can't be made.")
+
+ op.add_option('--max-drift',
+ nargs=1, type="int",
+ dest='max_drift', default=SCons.Node.FS.default_max_drift,
+ action="store",
+ help="Set maximum system clock drift to N seconds.",
+ metavar="N")
+
+ op.add_option('-n', '--no-exec', '--just-print', '--dry-run', '--recon',
+ dest='no_exec', default=False,
+ action="store_true",
+ help="Don't build; just print commands.")
+
+ op.add_option('--no-site-dir',
+ dest='no_site_dir', default=False,
+ action="store_true",
+ help="Don't search or use the usual site_scons dir.")
+
+ op.add_option('--profile',
+ nargs=1,
+ dest="profile_file", default=None,
+ action="store",
+ help="Profile SCons and put results in FILE.",
+ metavar="FILE")
+
+ op.add_option('-q', '--question',
+ dest="question", default=False,
+ action="store_true",
+ help="Don't build; exit status says if up to date.")
+
+ op.add_option('-Q',
+ dest='no_progress', default=False,
+ action="store_true",
+ help="Suppress \"Reading/Building\" progress messages.")
+
+ op.add_option('--random',
+ dest="random", default=False,
+ action="store_true",
+ help="Build dependencies in random order.")
+
+ op.add_option('-s', '--silent', '--quiet',
+ dest="silent", default=False,
+ action="store_true",
+ help="Don't print commands.")
+
+ op.add_option('--site-dir',
+ nargs=1,
+ dest='site_dir', default=None,
+ action="store",
+ help="Use DIR instead of the usual site_scons dir.",
+ metavar="DIR")
+
+ op.add_option('--taskmastertrace',
+ nargs=1,
+ dest="taskmastertrace_file", default=None,
+ action="store",
+ help="Trace Node evaluation to FILE.",
+ metavar="FILE")
+
+ tree_options = ["all", "derived", "prune", "status"]
+
+ def opt_tree(option, opt, value, parser, tree_options=tree_options):
+ import Main
+ tp = Main.TreePrinter()
+ for o in string.split(value, ','):
+ if o == 'all':
+ tp.derived = False
+ elif o == 'derived':
+ tp.derived = True
+ elif o == 'prune':
+ tp.prune = True
+ elif o == 'status':
+ tp.status = True
+ else:
+ raise OptionValueError("Warning: %s is not a valid --tree option" % o)
+ parser.values.tree_printers.append(tp)
+
+ opt_tree_help = "Print a dependency tree in various formats: %s." \
+ % string.join(tree_options, ", ")
+
+ op.add_option('--tree',
+ nargs=1, type="string",
+ dest="tree_printers", default=[],
+ action="callback", callback=opt_tree,
+ help=opt_tree_help,
+ metavar="OPTIONS")
+
+ op.add_option('-u', '--up', '--search-up',
+ dest="climb_up", default=0,
+ action="store_const", const=1,
+ help="Search up directory tree for SConstruct, "
+ "build targets at or below current directory.")
+
+ op.add_option('-U',
+ dest="climb_up", default=0,
+ action="store_const", const=3,
+ 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')
+ sys.exit(0)
+ op.add_option("-v", "--version",
+ action="callback", callback=opt_version,
+ help="Print the SCons version number and exit.")
+
+ op.add_option('--warn', '--warning',
+ nargs=1,
+ dest="warn", default=None,
+ action="store",
+ help="Enable or disable warnings.",
+ metavar="WARNING-SPEC")
+
+ op.add_option('-Y', '--repository', '--srcdir',
+ nargs=1,
+ dest="repository", default=[],
+ action="append",
+ help="Search REPOSITORY for source and target files.")
+
+ # Options from Make and Cons classic that we do not yet support,
+ # but which we may support someday and whose (potential) meanings
+ # we don't want to change. These all get a "the -X option is not
+ # yet implemented" message and don't show up in the help output.
+
+ op.add_option('-e', '--environment-overrides',
+ dest="environment_overrides",
+ action="callback", callback=opt_not_yet,
+ # help="Environment variables override makefiles."
+ help=SUPPRESS_HELP)
+ op.add_option('-l', '--load-average', '--max-load',
+ nargs=1, type="int",
+ dest="load_average", default=0,
+ action="callback", callback=opt_not_yet,
+ # action="store",
+ # help="Don't start multiple jobs unless load is below "
+ # "LOAD-AVERAGE."
+ help=SUPPRESS_HELP)
+ op.add_option('--list-actions',
+ dest="list_actions",
+ action="callback", callback=opt_not_yet,
+ # help="Don't build; list files and build actions."
+ help=SUPPRESS_HELP)
+ op.add_option('--list-derived',
+ dest="list_derived",
+ action="callback", callback=opt_not_yet,
+ # help="Don't build; list files that would be built."
+ help=SUPPRESS_HELP)
+ op.add_option('--list-where',
+ dest="list_where",
+ action="callback", callback=opt_not_yet,
+ # help="Don't build; list files and where defined."
+ help=SUPPRESS_HELP)
+ op.add_option('-o', '--old-file', '--assume-old',
+ nargs=1, type="string",
+ dest="old_file", default=[],
+ action="callback", callback=opt_not_yet,
+ # action="append",
+ # help = "Consider FILE to be old; don't rebuild it."
+ help=SUPPRESS_HELP)
+ op.add_option('--override',
+ nargs=1, type="string",
+ action="callback", callback=opt_not_yet,
+ dest="override",
+ # help="Override variables as specified in FILE."
+ help=SUPPRESS_HELP)
+ op.add_option('-p',
+ action="callback", callback=opt_not_yet,
+ dest="p",
+ # help="Print internal environments/objects."
+ help=SUPPRESS_HELP)
+ op.add_option('-r', '-R', '--no-builtin-rules', '--no-builtin-variables',
+ action="callback", callback=opt_not_yet,
+ dest="no_builtin_rules",
+ # help="Clear default environments and variables."
+ help=SUPPRESS_HELP)
+ op.add_option('-w', '--print-directory',
+ action="callback", callback=opt_not_yet,
+ dest="print_directory",
+ # help="Print the current directory."
+ help=SUPPRESS_HELP)
+ op.add_option('--no-print-directory',
+ action="callback", callback=opt_not_yet,
+ dest="no_print_directory",
+ # help="Turn off -w, even if it was turned on implicitly."
+ help=SUPPRESS_HELP)
+ op.add_option('--write-filenames',
+ nargs=1, type="string",
+ dest="write_filenames",
+ action="callback", callback=opt_not_yet,
+ # help="Write all filenames examined into FILE."
+ help=SUPPRESS_HELP)
+ op.add_option('-W', '--new-file', '--assume-new', '--what-if',
+ nargs=1, type="string",
+ dest="new_file",
+ action="callback", callback=opt_not_yet,
+ # help="Consider FILE to be changed."
+ help=SUPPRESS_HELP)
+ op.add_option('--warn-undefined-variables',
+ dest="warn_undefined_variables",
+ action="callback", callback=opt_not_yet,
+ # help="Warn when an undefined variable is referenced."
+ help=SUPPRESS_HELP)
+
+ return op
diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py
index d25e44c..7326c0c 100644
--- a/src/engine/SCons/Script/SConscript.py
+++ b/src/engine/SCons/Script/SConscript.py
@@ -469,7 +469,7 @@ class SConsEnvironment(SCons.Environment.Base):
def GetOption(self, name):
name = self.subst(name)
- return SCons.Script.Main.ssoptions.get(name)
+ return getattr(SCons.Script.Main.OptionsParser.values, name)
def Help(self, text):
text = self.subst(text, raw=1)
@@ -525,7 +525,7 @@ class SConsEnvironment(SCons.Environment.Base):
def SetOption(self, name, value):
name = self.subst(name)
- SCons.Script.Main.ssoptions.set(name, value)
+ SCons.Script.Main.OptionsParser.values.set_option(name, value)
#
#
diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py
index e970989..8f97c41 100644
--- a/src/engine/SCons/Script/__init__.py
+++ b/src/engine/SCons/Script/__init__.py
@@ -70,7 +70,7 @@ if "--debug=memoizer" in _args:
# for it to be displayed or not after warnings are configured.
import Main
exc_type, exc_value, tb = sys.exc_info()
- Main.delayed_warnings.append(exc_type, exc_value)
+ Main.delayed_warnings.append((exc_type, exc_value))
del _args
import SCons.Action
@@ -105,23 +105,23 @@ BuildTask = Main.BuildTask
CleanTask = Main.CleanTask
QuestionTask = Main.QuestionTask
#PrintHelp = Main.PrintHelp
-OptParser = Main.OptParser
-SConscriptSettableOptions = Main.SConscriptSettableOptions
+#SConscriptSettableOptions = Main.SConscriptSettableOptions
+AddOption = Main.AddOption
-keep_going_on_error = Main.keep_going_on_error
+#keep_going_on_error = Main.keep_going_on_error
#print_dtree = Main.print_dtree
-print_explanations = Main.print_explanations
-print_includes = Main.print_includes
-print_objects = Main.print_objects
-print_time = Main.print_time
+#print_explanations = Main.print_explanations
+#print_includes = Main.print_includes
+#print_objects = Main.print_objects
+#print_time = Main.print_time
#print_tree = Main.print_tree
-memory_stats = Main.memory_stats
-ignore_errors = Main.ignore_errors
+#memory_stats = Main.memory_stats
+#ignore_errors = Main.ignore_errors
#sconscript_time = Main.sconscript_time
#command_time = Main.command_time
#exit_status = Main.exit_status
#profiling = Main.profiling
-repositories = Main.repositories
+#repositories = Main.repositories
#
import SConscript
@@ -138,6 +138,7 @@ Builder = SCons.Builder.Builder
Configure = _SConscript.Configure
EnumOption = SCons.Options.EnumOption
Environment = SCons.Environment.Environment
+#OptParser = SCons.SConsOptions.OptParser
FindPathDirs = SCons.Scanner.FindPathDirs
ListOption = SCons.Options.ListOption
PackageOption = SCons.Options.PackageOption
diff --git a/src/engine/SCons/Warnings.py b/src/engine/SCons/Warnings.py
index b313ee0..b1d39ec 100644
--- a/src/engine/SCons/Warnings.py
+++ b/src/engine/SCons/Warnings.py
@@ -64,6 +64,9 @@ class NoMD5ModuleWarning(Warning):
class NoMetaclassSupportWarning(Warning):
pass
+class NoObjectCountWarning(Warning):
+ pass
+
class NoParallelSupportWarning(Warning):
pass
diff --git a/src/engine/SCons/compat/__init__.py b/src/engine/SCons/compat/__init__.py
index 959bf52..0dfb18e 100644
--- a/src/engine/SCons/compat/__init__.py
+++ b/src/engine/SCons/compat/__init__.py
@@ -98,6 +98,20 @@ except NameError:
import sets
__builtin__.set = sets.Set
+# If we need the compatibility version of textwrap, it must be imported
+# before optparse, which uses it.
+try:
+ import textwrap
+except ImportError:
+ # Pre-2.3 Python has no textwrap module.
+ import_as('_scons_textwrap', 'textwrap')
+
+try:
+ import optparse
+except ImportError:
+ # Pre-2.3 Python has no optparse module.
+ import_as('_scons_optparse', 'optparse')
+
try:
import subprocess
except ImportError:
diff --git a/src/engine/SCons/compat/_scons_optparse.py b/src/engine/SCons/compat/_scons_optparse.py
new file mode 100644
index 0000000..6b37687
--- /dev/null
+++ b/src/engine/SCons/compat/_scons_optparse.py
@@ -0,0 +1,1719 @@
+"""optparse - a powerful, extensible, and easy-to-use option parser.
+
+By Greg Ward <gward@python.net>
+
+Originally distributed as Optik; see http://optik.sourceforge.net/ .
+
+If you have problems with this module, please do not file bugs,
+patches, or feature requests with Python; instead, use Optik's
+SourceForge project page:
+ http://sourceforge.net/projects/optik
+
+For support, use the optik-users@lists.sourceforge.net mailing list
+(http://lists.sourceforge.net/lists/listinfo/optik-users).
+"""
+
+# Python developers: please do not make changes to this file, since
+# it is automatically generated from the Optik source code.
+
+__version__ = "1.5.3"
+
+__all__ = ['Option',
+ 'SUPPRESS_HELP',
+ 'SUPPRESS_USAGE',
+ 'Values',
+ 'OptionContainer',
+ 'OptionGroup',
+ 'OptionParser',
+ 'HelpFormatter',
+ 'IndentedHelpFormatter',
+ 'TitledHelpFormatter',
+ 'OptParseError',
+ 'OptionError',
+ 'OptionConflictError',
+ 'OptionValueError',
+ 'BadOptionError']
+
+__copyright__ = """
+Copyright (c) 2001-2006 Gregory P. Ward. All rights reserved.
+Copyright (c) 2002-2006 Python Software Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the author nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+"""
+
+import string
+import sys, os
+import types
+import textwrap
+
+def _repr(self):
+ return "<%s at 0x%x: %s>" % (self.__class__.__name__, id(self), self)
+
+
+try:
+ sys.getdefaultencoding
+except AttributeError:
+ def fake_getdefaultencoding():
+ return None
+ sys.getdefaultencoding = fake_getdefaultencoding
+
+try:
+ ''.encode
+except AttributeError:
+ def encode_wrapper(s, encoding, replacement):
+ return s
+else:
+ def encode_wrapper(s, encoding, replacement):
+ return s.encode(encoding, replacement)
+
+
+# This file was generated from:
+# Id: option_parser.py 527 2006-07-23 15:21:30Z greg
+# Id: option.py 522 2006-06-11 16:22:03Z gward
+# Id: help.py 527 2006-07-23 15:21:30Z greg
+# Id: errors.py 509 2006-04-20 00:58:24Z gward
+
+try:
+ from gettext import gettext
+except ImportError:
+ def gettext(message):
+ return message
+_ = gettext
+
+
+class OptParseError (Exception):
+ def __init__(self, msg):
+ self.msg = msg
+
+ def __str__(self):
+ return self.msg
+
+
+class OptionError (OptParseError):
+ """
+ Raised if an Option instance is created with invalid or
+ inconsistent arguments.
+ """
+
+ def __init__(self, msg, option):
+ self.msg = msg
+ self.option_id = str(option)
+
+ def __str__(self):
+ if self.option_id:
+ return "option %s: %s" % (self.option_id, self.msg)
+ else:
+ return self.msg
+
+class OptionConflictError (OptionError):
+ """
+ Raised if conflicting options are added to an OptionParser.
+ """
+
+class OptionValueError (OptParseError):
+ """
+ Raised if an invalid option value is encountered on the command
+ line.
+ """
+
+class BadOptionError (OptParseError):
+ """
+ Raised if an invalid option is seen on the command line.
+ """
+ def __init__(self, opt_str):
+ self.opt_str = opt_str
+
+ def __str__(self):
+ return _("no such option: %s") % self.opt_str
+
+class AmbiguousOptionError (BadOptionError):
+ """
+ Raised if an ambiguous option is seen on the command line.
+ """
+ def __init__(self, opt_str, possibilities):
+ BadOptionError.__init__(self, opt_str)
+ self.possibilities = possibilities
+
+ def __str__(self):
+ return (_("ambiguous option: %s (%s?)")
+ % (self.opt_str, string.join(self.possibilities, ", ")))
+
+
+class HelpFormatter:
+
+ """
+ Abstract base class for formatting option help. OptionParser
+ instances should use one of the HelpFormatter subclasses for
+ formatting help; by default IndentedHelpFormatter is used.
+
+ Instance attributes:
+ parser : OptionParser
+ the controlling OptionParser instance
+ indent_increment : int
+ the number of columns to indent per nesting level
+ max_help_position : int
+ the maximum starting column for option help text
+ help_position : int
+ the calculated starting column for option help text;
+ initially the same as the maximum
+ width : int
+ total number of columns for output (pass None to constructor for
+ this value to be taken from the $COLUMNS environment variable)
+ level : int
+ current indentation level
+ current_indent : int
+ current indentation level (in columns)
+ help_width : int
+ number of columns available for option help text (calculated)
+ default_tag : str
+ text to replace with each option's default value, "%default"
+ by default. Set to false value to disable default value expansion.
+ option_strings : { Option : str }
+ maps Option instances to the snippet of help text explaining
+ the syntax of that option, e.g. "-h, --help" or
+ "-fFILE, --file=FILE"
+ _short_opt_fmt : str
+ format string controlling how short options with values are
+ printed in help text. Must be either "%s%s" ("-fFILE") or
+ "%s %s" ("-f FILE"), because those are the two syntaxes that
+ Optik supports.
+ _long_opt_fmt : str
+ similar but for long options; must be either "%s %s" ("--file FILE")
+ or "%s=%s" ("--file=FILE").
+ """
+
+ NO_DEFAULT_VALUE = "none"
+
+ def __init__(self,
+ indent_increment,
+ max_help_position,
+ width,
+ short_first):
+ self.parser = None
+ self.indent_increment = indent_increment
+ self.help_position = self.max_help_position = max_help_position
+ if width is None:
+ try:
+ width = int(os.environ['COLUMNS'])
+ except (KeyError, ValueError):
+ width = 80
+ width = width - 2
+ self.width = width
+ self.current_indent = 0
+ self.level = 0
+ self.help_width = None # computed later
+ self.short_first = short_first
+ self.default_tag = "%default"
+ self.option_strings = {}
+ self._short_opt_fmt = "%s %s"
+ self._long_opt_fmt = "%s=%s"
+
+ def set_parser(self, parser):
+ self.parser = parser
+
+ def set_short_opt_delimiter(self, delim):
+ if delim not in ("", " "):
+ raise ValueError(
+ "invalid metavar delimiter for short options: %r" % delim)
+ self._short_opt_fmt = "%s" + delim + "%s"
+
+ def set_long_opt_delimiter(self, delim):
+ if delim not in ("=", " "):
+ raise ValueError(
+ "invalid metavar delimiter for long options: %r" % delim)
+ self._long_opt_fmt = "%s" + delim + "%s"
+
+ def indent(self):
+ self.current_indent = self.current_indent + self.indent_increment
+ self.level = self.level + 1
+
+ def dedent(self):
+ self.current_indent = self.current_indent - self.indent_increment
+ assert self.current_indent >= 0, "Indent decreased below 0."
+ self.level = self.level - 1
+
+ def format_usage(self, usage):
+ raise NotImplementedError, "subclasses must implement"
+
+ def format_heading(self, heading):
+ raise NotImplementedError, "subclasses must implement"
+
+ def _format_text(self, text):
+ """
+ Format a paragraph of free-form text for inclusion in the
+ help output at the current indentation level.
+ """
+ text_width = self.width - self.current_indent
+ indent = " "*self.current_indent
+ return textwrap.fill(text,
+ text_width,
+ initial_indent=indent,
+ subsequent_indent=indent)
+
+ def format_description(self, description):
+ if description:
+ return self._format_text(description) + "\n"
+ else:
+ return ""
+
+ def format_epilog(self, epilog):
+ if epilog:
+ return "\n" + self._format_text(epilog) + "\n"
+ else:
+ return ""
+
+
+ def expand_default(self, option):
+ if self.parser is None or not self.default_tag:
+ return option.help
+
+ default_value = self.parser.defaults.get(option.dest)
+ if default_value is NO_DEFAULT or default_value is None:
+ default_value = self.NO_DEFAULT_VALUE
+
+ return string.replace(option.help, self.default_tag, str(default_value))
+
+ def format_option(self, option):
+ # The help for each option consists of two parts:
+ # * the opt strings and metavars
+ # eg. ("-x", or "-fFILENAME, --file=FILENAME")
+ # * the user-supplied help string
+ # eg. ("turn on expert mode", "read data from FILENAME")
+ #
+ # If possible, we write both of these on the same line:
+ # -x turn on expert mode
+ #
+ # But if the opt string list is too long, we put the help
+ # string on a second line, indented to the same column it would
+ # start in if it fit on the first line.
+ # -fFILENAME, --file=FILENAME
+ # read data from FILENAME
+ result = []
+ opts = self.option_strings[option]
+ opt_width = self.help_position - self.current_indent - 2
+ if len(opts) > opt_width:
+ opts = "%*s%s\n" % (self.current_indent, "", opts)
+ indent_first = self.help_position
+ else: # start help on same line as opts
+ opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts)
+ indent_first = 0
+ result.append(opts)
+ if option.help:
+ help_text = self.expand_default(option)
+ help_lines = textwrap.wrap(help_text, self.help_width)
+ result.append("%*s%s\n" % (indent_first, "", help_lines[0]))
+ for line in help_lines[1:]:
+ result.append("%*s%s\n" % (self.help_position, "", line))
+ elif opts[-1] != "\n":
+ result.append("\n")
+ return string.join(result, "")
+
+ def store_option_strings(self, parser):
+ self.indent()
+ max_len = 0
+ for opt in parser.option_list:
+ strings = self.format_option_strings(opt)
+ self.option_strings[opt] = strings
+ max_len = max(max_len, len(strings) + self.current_indent)
+ self.indent()
+ for group in parser.option_groups:
+ for opt in group.option_list:
+ strings = self.format_option_strings(opt)
+ self.option_strings[opt] = strings
+ max_len = max(max_len, len(strings) + self.current_indent)
+ self.dedent()
+ self.dedent()
+ self.help_position = min(max_len + 2, self.max_help_position)
+ self.help_width = self.width - self.help_position
+
+ def format_option_strings(self, option):
+ """Return a comma-separated list of option strings & metavariables."""
+ if option.takes_value():
+ metavar = option.metavar or string.upper(option.dest)
+ short_opts = []
+ for sopt in option._short_opts:
+ short_opts.append(self._short_opt_fmt % (sopt, metavar))
+ long_opts = []
+ for lopt in option._long_opts:
+ long_opts.append(self._long_opt_fmt % (lopt, metavar))
+ else:
+ short_opts = option._short_opts
+ long_opts = option._long_opts
+
+ if self.short_first:
+ opts = short_opts + long_opts
+ else:
+ opts = long_opts + short_opts
+
+ return string.join(opts, ", ")
+
+class IndentedHelpFormatter (HelpFormatter):
+ """Format help with indented section bodies.
+ """
+
+ def __init__(self,
+ indent_increment=2,
+ max_help_position=24,
+ width=None,
+ short_first=1):
+ HelpFormatter.__init__(
+ self, indent_increment, max_help_position, width, short_first)
+
+ def format_usage(self, usage):
+ return _("Usage: %s\n") % usage
+
+ def format_heading(self, heading):
+ return "%*s%s:\n" % (self.current_indent, "", heading)
+
+
+class TitledHelpFormatter (HelpFormatter):
+ """Format help with underlined section headers.
+ """
+
+ def __init__(self,
+ indent_increment=0,
+ max_help_position=24,
+ width=None,
+ short_first=0):
+ HelpFormatter.__init__ (
+ self, indent_increment, max_help_position, width, short_first)
+
+ def format_usage(self, usage):
+ return "%s %s\n" % (self.format_heading(_("Usage")), usage)
+
+ def format_heading(self, heading):
+ return "%s\n%s\n" % (heading, "=-"[self.level] * len(heading))
+
+
+def _parse_num(val, type):
+ if string.lower(val[:2]) == "0x": # hexadecimal
+ radix = 16
+ elif string.lower(val[:2]) == "0b": # binary
+ radix = 2
+ val = val[2:] or "0" # have to remove "0b" prefix
+ elif val[:1] == "0": # octal
+ radix = 8
+ else: # decimal
+ radix = 10
+
+ return type(val, radix)
+
+def _parse_int(val):
+ return _parse_num(val, int)
+
+def _parse_long(val):
+ return _parse_num(val, long)
+
+try:
+ int('0', 10)
+except TypeError:
+ # Python 1.5.2 doesn't allow a radix value to be passed to int().
+ _parse_int = int
+
+try:
+ long('0', 10)
+except TypeError:
+ # Python 1.5.2 doesn't allow a radix value to be passed to long().
+ _parse_long = long
+
+_builtin_cvt = { "int" : (_parse_int, _("integer")),
+ "long" : (_parse_long, _("long integer")),
+ "float" : (float, _("floating-point")),
+ "complex" : (complex, _("complex")) }
+
+def check_builtin(option, opt, value):
+ (cvt, what) = _builtin_cvt[option.type]
+ try:
+ return cvt(value)
+ except ValueError:
+ raise OptionValueError(
+ _("option %s: invalid %s value: %r") % (opt, what, value))
+
+def check_choice(option, opt, value):
+ if value in option.choices:
+ return value
+ else:
+ choices = string.join(map(repr, option.choices), ", ")
+ raise OptionValueError(
+ _("option %s: invalid choice: %r (choose from %s)")
+ % (opt, value, choices))
+
+# Not supplying a default is different from a default of None,
+# so we need an explicit "not supplied" value.
+NO_DEFAULT = ("NO", "DEFAULT")
+
+
+class Option:
+ """
+ Instance attributes:
+ _short_opts : [string]
+ _long_opts : [string]
+
+ action : string
+ type : string
+ dest : string
+ default : any
+ nargs : int
+ const : any
+ choices : [string]
+ callback : function
+ callback_args : (any*)
+ callback_kwargs : { string : any }
+ help : string
+ metavar : string
+ """
+
+ # The list of instance attributes that may be set through
+ # keyword args to the constructor.
+ ATTRS = ['action',
+ 'type',
+ 'dest',
+ 'default',
+ 'nargs',
+ 'const',
+ 'choices',
+ 'callback',
+ 'callback_args',
+ 'callback_kwargs',
+ 'help',
+ 'metavar']
+
+ # The set of actions allowed by option parsers. Explicitly listed
+ # here so the constructor can validate its arguments.
+ ACTIONS = ("store",
+ "store_const",
+ "store_true",
+ "store_false",
+ "append",
+ "append_const",
+ "count",
+ "callback",
+ "help",
+ "version")
+
+ # The set of actions that involve storing a value somewhere;
+ # also listed just for constructor argument validation. (If
+ # the action is one of these, there must be a destination.)
+ STORE_ACTIONS = ("store",
+ "store_const",
+ "store_true",
+ "store_false",
+ "append",
+ "append_const",
+ "count")
+
+ # The set of actions for which it makes sense to supply a value
+ # type, ie. which may consume an argument from the command line.
+ TYPED_ACTIONS = ("store",
+ "append",
+ "callback")
+
+ # The set of actions which *require* a value type, ie. that
+ # always consume an argument from the command line.
+ ALWAYS_TYPED_ACTIONS = ("store",
+ "append")
+
+ # The set of actions which take a 'const' attribute.
+ CONST_ACTIONS = ("store_const",
+ "append_const")
+
+ # The set of known types for option parsers. Again, listed here for
+ # constructor argument validation.
+ TYPES = ("string", "int", "long", "float", "complex", "choice")
+
+ # Dictionary of argument checking functions, which convert and
+ # validate option arguments according to the option type.
+ #
+ # Signature of checking functions is:
+ # check(option : Option, opt : string, value : string) -> any
+ # where
+ # option is the Option instance calling the checker
+ # opt is the actual option seen on the command-line
+ # (eg. "-a", "--file")
+ # value is the option argument seen on the command-line
+ #
+ # The return value should be in the appropriate Python type
+ # for option.type -- eg. an integer if option.type == "int".
+ #
+ # If no checker is defined for a type, arguments will be
+ # unchecked and remain strings.
+ TYPE_CHECKER = { "int" : check_builtin,
+ "long" : check_builtin,
+ "float" : check_builtin,
+ "complex": check_builtin,
+ "choice" : check_choice,
+ }
+
+
+ # CHECK_METHODS is a list of unbound method objects; they are called
+ # by the constructor, in order, after all attributes are
+ # initialized. The list is created and filled in later, after all
+ # the methods are actually defined. (I just put it here because I
+ # like to define and document all class attributes in the same
+ # place.) Subclasses that add another _check_*() method should
+ # define their own CHECK_METHODS list that adds their check method
+ # to those from this class.
+ CHECK_METHODS = None
+
+
+ # -- Constructor/initialization methods ----------------------------
+
+ def __init__(self, *opts, **attrs):
+ # Set _short_opts, _long_opts attrs from 'opts' tuple.
+ # Have to be set now, in case no option strings are supplied.
+ self._short_opts = []
+ self._long_opts = []
+ opts = self._check_opt_strings(opts)
+ self._set_opt_strings(opts)
+
+ # Set all other attrs (action, type, etc.) from 'attrs' dict
+ self._set_attrs(attrs)
+
+ # Check all the attributes we just set. There are lots of
+ # complicated interdependencies, but luckily they can be farmed
+ # out to the _check_*() methods listed in CHECK_METHODS -- which
+ # could be handy for subclasses! The one thing these all share
+ # is that they raise OptionError if they discover a problem.
+ for checker in self.CHECK_METHODS:
+ checker(self)
+
+ def _check_opt_strings(self, opts):
+ # Filter out None because early versions of Optik had exactly
+ # one short option and one long option, either of which
+ # could be None.
+ opts = filter(None, opts)
+ if not opts:
+ raise TypeError("at least one option string must be supplied")
+ return opts
+
+ def _set_opt_strings(self, opts):
+ for opt in opts:
+ if len(opt) < 2:
+ raise OptionError(
+ "invalid option string %r: "
+ "must be at least two characters long" % opt, self)
+ elif len(opt) == 2:
+ if not (opt[0] == "-" and opt[1] != "-"):
+ raise OptionError(
+ "invalid short option string %r: "
+ "must be of the form -x, (x any non-dash char)" % opt,
+ self)
+ self._short_opts.append(opt)
+ else:
+ if not (opt[0:2] == "--" and opt[2] != "-"):
+ raise OptionError(
+ "invalid long option string %r: "
+ "must start with --, followed by non-dash" % opt,
+ self)
+ self._long_opts.append(opt)
+
+ def _set_attrs(self, attrs):
+ for attr in self.ATTRS:
+ if attrs.has_key(attr):
+ setattr(self, attr, attrs[attr])
+ del attrs[attr]
+ else:
+ if attr == 'default':
+ setattr(self, attr, NO_DEFAULT)
+ else:
+ setattr(self, attr, None)
+ if attrs:
+ attrs = attrs.keys()
+ attrs.sort()
+ raise OptionError(
+ "invalid keyword arguments: %s" % string.join(attrs, ", "),
+ self)
+
+
+ # -- Constructor validation methods --------------------------------
+
+ def _check_action(self):
+ if self.action is None:
+ self.action = "store"
+ elif self.action not in self.ACTIONS:
+ raise OptionError("invalid action: %r" % self.action, self)
+
+ def _check_type(self):
+ if self.type is None:
+ if self.action in self.ALWAYS_TYPED_ACTIONS:
+ if self.choices is not None:
+ # The "choices" attribute implies "choice" type.
+ self.type = "choice"
+ else:
+ # No type given? "string" is the most sensible default.
+ self.type = "string"
+ else:
+ # Allow type objects or builtin type conversion functions
+ # (int, str, etc.) as an alternative to their names. (The
+ # complicated check of __builtin__ is only necessary for
+ # Python 2.1 and earlier, and is short-circuited by the
+ # first check on modern Pythons.)
+ import __builtin__
+ if ( type(self.type) is types.TypeType or
+ (hasattr(self.type, "__name__") and
+ getattr(__builtin__, self.type.__name__, None) is self.type) ):
+ self.type = self.type.__name__
+
+ if self.type == "str":
+ self.type = "string"
+
+ if self.type not in self.TYPES:
+ raise OptionError("invalid option type: %r" % self.type, self)
+ if self.action not in self.TYPED_ACTIONS:
+ raise OptionError(
+ "must not supply a type for action %r" % self.action, self)
+
+ def _check_choice(self):
+ if self.type == "choice":
+ if self.choices is None:
+ raise OptionError(
+ "must supply a list of choices for type 'choice'", self)
+ elif type(self.choices) not in (types.TupleType, types.ListType):
+ raise OptionError(
+ "choices must be a list of strings ('%s' supplied)"
+ % string.split(str(type(self.choices)), "'")[1], self)
+ elif self.choices is not None:
+ raise OptionError(
+ "must not supply choices for type %r" % self.type, self)
+
+ def _check_dest(self):
+ # No destination given, and we need one for this action. The
+ # self.type check is for callbacks that take a value.
+ takes_value = (self.action in self.STORE_ACTIONS or
+ self.type is not None)
+ if self.dest is None and takes_value:
+
+ # Glean a destination from the first long option string,
+ # or from the first short option string if no long options.
+ if self._long_opts:
+ # eg. "--foo-bar" -> "foo_bar"
+ self.dest = string.replace(self._long_opts[0][2:], '-', '_')
+ else:
+ self.dest = self._short_opts[0][1]
+
+ def _check_const(self):
+ if self.action not in self.CONST_ACTIONS and self.const is not None:
+ raise OptionError(
+ "'const' must not be supplied for action %r" % self.action,
+ self)
+
+ def _check_nargs(self):
+ if self.action in self.TYPED_ACTIONS:
+ if self.nargs is None:
+ self.nargs = 1
+ elif self.nargs is not None:
+ raise OptionError(
+ "'nargs' must not be supplied for action %r" % self.action,
+ self)
+
+ def _check_callback(self):
+ if self.action == "callback":
+ if not callable(self.callback):
+ raise OptionError(
+ "callback not callable: %r" % self.callback, self)
+ if (self.callback_args is not None and
+ type(self.callback_args) is not types.TupleType):
+ raise OptionError(
+ "callback_args, if supplied, must be a tuple: not %r"
+ % self.callback_args, self)
+ if (self.callback_kwargs is not None and
+ type(self.callback_kwargs) is not types.DictType):
+ raise OptionError(
+ "callback_kwargs, if supplied, must be a dict: not %r"
+ % self.callback_kwargs, self)
+ else:
+ if self.callback is not None:
+ raise OptionError(
+ "callback supplied (%r) for non-callback option"
+ % self.callback, self)
+ if self.callback_args is not None:
+ raise OptionError(
+ "callback_args supplied for non-callback option", self)
+ if self.callback_kwargs is not None:
+ raise OptionError(
+ "callback_kwargs supplied for non-callback option", self)
+
+
+ CHECK_METHODS = [_check_action,
+ _check_type,
+ _check_choice,
+ _check_dest,
+ _check_const,
+ _check_nargs,
+ _check_callback]
+
+
+ # -- Miscellaneous methods -----------------------------------------
+
+ def __str__(self):
+ return string.join(self._short_opts + self._long_opts, "/")
+
+ __repr__ = _repr
+
+ def takes_value(self):
+ return self.type is not None
+
+ def get_opt_string(self):
+ if self._long_opts:
+ return self._long_opts[0]
+ else:
+ return self._short_opts[0]
+
+
+ # -- Processing methods --------------------------------------------
+
+ def check_value(self, opt, value):
+ checker = self.TYPE_CHECKER.get(self.type)
+ if checker is None:
+ return value
+ else:
+ return checker(self, opt, value)
+
+ def convert_value(self, opt, value):
+ if value is not None:
+ if self.nargs == 1:
+ return self.check_value(opt, value)
+ else:
+ return tuple(map(lambda v, o=opt, s=self: s.check_value(o, v), value))
+
+ def process(self, opt, value, values, parser):
+
+ # First, convert the value(s) to the right type. Howl if any
+ # value(s) are bogus.
+ value = self.convert_value(opt, value)
+
+ # And then take whatever action is expected of us.
+ # This is a separate method to make life easier for
+ # subclasses to add new actions.
+ return self.take_action(
+ self.action, self.dest, opt, value, values, parser)
+
+ def take_action(self, action, dest, opt, value, values, parser):
+ if action == "store":
+ setattr(values, dest, value)
+ elif action == "store_const":
+ setattr(values, dest, self.const)
+ elif action == "store_true":
+ setattr(values, dest, True)
+ elif action == "store_false":
+ setattr(values, dest, False)
+ elif action == "append":
+ values.ensure_value(dest, []).append(value)
+ elif action == "append_const":
+ values.ensure_value(dest, []).append(self.const)
+ elif action == "count":
+ setattr(values, dest, values.ensure_value(dest, 0) + 1)
+ elif action == "callback":
+ args = self.callback_args or ()
+ kwargs = self.callback_kwargs or {}
+ apply(self.callback, (self, opt, value, parser,) + args, kwargs)
+ elif action == "help":
+ parser.print_help()
+ parser.exit()
+ elif action == "version":
+ parser.print_version()
+ parser.exit()
+ else:
+ raise RuntimeError, "unknown action %r" % self.action
+
+ return 1
+
+# class Option
+
+
+SUPPRESS_HELP = "SUPPRESS"+"HELP"
+SUPPRESS_USAGE = "SUPPRESS"+"USAGE"
+
+# For compatibility with Python 2.2
+try:
+ True, False
+except NameError:
+ (True, False) = (1, 0)
+
+try:
+ types.UnicodeType
+except AttributeError:
+ def isbasestring(x):
+ return isinstance(x, types.StringType)
+else:
+ def isbasestring(x):
+ return isinstance(x, types.StringType) or isinstance(x, types.UnicodeType)
+
+class Values:
+
+ def __init__(self, defaults=None):
+ if defaults:
+ for (attr, val) in defaults.items():
+ setattr(self, attr, val)
+
+ def __str__(self):
+ return str(self.__dict__)
+
+ __repr__ = _repr
+
+ def __cmp__(self, other):
+ if isinstance(other, Values):
+ return cmp(self.__dict__, other.__dict__)
+ elif isinstance(other, types.DictType):
+ return cmp(self.__dict__, other)
+ else:
+ return -1
+
+ def _update_careful(self, dict):
+ """
+ Update the option values from an arbitrary dictionary, but only
+ use keys from dict that already have a corresponding attribute
+ in self. Any keys in dict without a corresponding attribute
+ are silently ignored.
+ """
+ for attr in dir(self):
+ if dict.has_key(attr):
+ dval = dict[attr]
+ if dval is not None:
+ setattr(self, attr, dval)
+
+ def _update_loose(self, dict):
+ """
+ Update the option values from an arbitrary dictionary,
+ using all keys from the dictionary regardless of whether
+ they have a corresponding attribute in self or not.
+ """
+ self.__dict__.update(dict)
+
+ def _update(self, dict, mode):
+ if mode == "careful":
+ self._update_careful(dict)
+ elif mode == "loose":
+ self._update_loose(dict)
+ else:
+ raise ValueError, "invalid update mode: %r" % mode
+
+ def read_module(self, modname, mode="careful"):
+ __import__(modname)
+ mod = sys.modules[modname]
+ self._update(vars(mod), mode)
+
+ def read_file(self, filename, mode="careful"):
+ vars = {}
+ execfile(filename, vars)
+ self._update(vars, mode)
+
+ def ensure_value(self, attr, value):
+ if not hasattr(self, attr) or getattr(self, attr) is None:
+ setattr(self, attr, value)
+ return getattr(self, attr)
+
+
+class OptionContainer:
+
+ """
+ Abstract base class.
+
+ Class attributes:
+ standard_option_list : [Option]
+ list of standard options that will be accepted by all instances
+ of this parser class (intended to be overridden by subclasses).
+
+ Instance attributes:
+ option_list : [Option]
+ the list of Option objects contained by this OptionContainer
+ _short_opt : { string : Option }
+ dictionary mapping short option strings, eg. "-f" or "-X",
+ to the Option instances that implement them. If an Option
+ has multiple short option strings, it will appears in this
+ dictionary multiple times. [1]
+ _long_opt : { string : Option }
+ dictionary mapping long option strings, eg. "--file" or
+ "--exclude", to the Option instances that implement them.
+ Again, a given Option can occur multiple times in this
+ dictionary. [1]
+ defaults : { string : any }
+ dictionary mapping option destination names to default
+ values for each destination [1]
+
+ [1] These mappings are common to (shared by) all components of the
+ controlling OptionParser, where they are initially created.
+
+ """
+
+ def __init__(self, option_class, conflict_handler, description):
+ # Initialize the option list and related data structures.
+ # This method must be provided by subclasses, and it must
+ # initialize at least the following instance attributes:
+ # option_list, _short_opt, _long_opt, defaults.
+ self._create_option_list()
+
+ self.option_class = option_class
+ self.set_conflict_handler(conflict_handler)
+ self.set_description(description)
+
+ def _create_option_mappings(self):
+ # For use by OptionParser constructor -- create the master
+ # option mappings used by this OptionParser and all
+ # OptionGroups that it owns.
+ self._short_opt = {} # single letter -> Option instance
+ self._long_opt = {} # long option -> Option instance
+ self.defaults = {} # maps option dest -> default value
+
+
+ def _share_option_mappings(self, parser):
+ # For use by OptionGroup constructor -- use shared option
+ # mappings from the OptionParser that owns this OptionGroup.
+ self._short_opt = parser._short_opt
+ self._long_opt = parser._long_opt
+ self.defaults = parser.defaults
+
+ def set_conflict_handler(self, handler):
+ if handler not in ("error", "resolve"):
+ raise ValueError, "invalid conflict_resolution value %r" % handler
+ self.conflict_handler = handler
+
+ def set_description(self, description):
+ self.description = description
+
+ def get_description(self):
+ return self.description
+
+
+ def destroy(self):
+ """see OptionParser.destroy()."""
+ del self._short_opt
+ del self._long_opt
+ del self.defaults
+
+
+ # -- Option-adding methods -----------------------------------------
+
+ def _check_conflict(self, option):
+ conflict_opts = []
+ for opt in option._short_opts:
+ if self._short_opt.has_key(opt):
+ conflict_opts.append((opt, self._short_opt[opt]))
+ for opt in option._long_opts:
+ if self._long_opt.has_key(opt):
+ conflict_opts.append((opt, self._long_opt[opt]))
+
+ if conflict_opts:
+ handler = self.conflict_handler
+ if handler == "error":
+ raise OptionConflictError(
+ "conflicting option string(s): %s"
+ % string.join(map(lambda co: co[0], conflict_opts), ", "),
+ option)
+ elif handler == "resolve":
+ for (opt, c_option) in conflict_opts:
+ if opt[:2] == "--":
+ c_option._long_opts.remove(opt)
+ del self._long_opt[opt]
+ else:
+ c_option._short_opts.remove(opt)
+ del self._short_opt[opt]
+ if not (c_option._short_opts or c_option._long_opts):
+ c_option.container.option_list.remove(c_option)
+
+ def add_option(self, *args, **kwargs):
+ """add_option(Option)
+ add_option(opt_str, ..., kwarg=val, ...)
+ """
+ if type(args[0]) is types.StringType:
+ option = apply(self.option_class, args, kwargs)
+ elif len(args) == 1 and not kwargs:
+ option = args[0]
+ if not isinstance(option, Option):
+ raise TypeError, "not an Option instance: %r" % option
+ else:
+ raise TypeError, "invalid arguments"
+
+ self._check_conflict(option)
+
+ self.option_list.append(option)
+ option.container = self
+ for opt in option._short_opts:
+ self._short_opt[opt] = option
+ for opt in option._long_opts:
+ self._long_opt[opt] = option
+
+ if option.dest is not None: # option has a dest, we need a default
+ if option.default is not NO_DEFAULT:
+ self.defaults[option.dest] = option.default
+ elif not self.defaults.has_key(option.dest):
+ self.defaults[option.dest] = None
+
+ return option
+
+ def add_options(self, option_list):
+ for option in option_list:
+ self.add_option(option)
+
+ # -- Option query/removal methods ----------------------------------
+
+ def get_option(self, opt_str):
+ return (self._short_opt.get(opt_str) or
+ self._long_opt.get(opt_str))
+
+ def has_option(self, opt_str):
+ return (self._short_opt.has_key(opt_str) or
+ self._long_opt.has_key(opt_str))
+
+ def remove_option(self, opt_str):
+ option = self._short_opt.get(opt_str)
+ if option is None:
+ option = self._long_opt.get(opt_str)
+ if option is None:
+ raise ValueError("no such option %r" % opt_str)
+
+ for opt in option._short_opts:
+ del self._short_opt[opt]
+ for opt in option._long_opts:
+ del self._long_opt[opt]
+ option.container.option_list.remove(option)
+
+
+ # -- Help-formatting methods ---------------------------------------
+
+ def format_option_help(self, formatter):
+ if not self.option_list:
+ return ""
+ result = []
+ for option in self.option_list:
+ if not option.help is SUPPRESS_HELP:
+ result.append(formatter.format_option(option))
+ return string.join(result, "")
+
+ def format_description(self, formatter):
+ return formatter.format_description(self.get_description())
+
+ def format_help(self, formatter):
+ result = []
+ if self.description:
+ result.append(self.format_description(formatter))
+ if self.option_list:
+ result.append(self.format_option_help(formatter))
+ return string.join(result, "\n")
+
+
+class OptionGroup (OptionContainer):
+
+ def __init__(self, parser, title, description=None):
+ self.parser = parser
+ OptionContainer.__init__(
+ self, parser.option_class, parser.conflict_handler, description)
+ self.title = title
+
+ def _create_option_list(self):
+ self.option_list = []
+ self._share_option_mappings(self.parser)
+
+ def set_title(self, title):
+ self.title = title
+
+ def destroy(self):
+ """see OptionParser.destroy()."""
+ OptionContainer.destroy(self)
+ del self.option_list
+
+ # -- Help-formatting methods ---------------------------------------
+
+ def format_help(self, formatter):
+ result = formatter.format_heading(self.title)
+ formatter.indent()
+ result = result + OptionContainer.format_help(self, formatter)
+ formatter.dedent()
+ return result
+
+
+class OptionParser (OptionContainer):
+
+ """
+ Class attributes:
+ standard_option_list : [Option]
+ list of standard options that will be accepted by all instances
+ of this parser class (intended to be overridden by subclasses).
+
+ Instance attributes:
+ usage : string
+ a usage string for your program. Before it is displayed
+ to the user, "%prog" will be expanded to the name of
+ your program (self.prog or os.path.basename(sys.argv[0])).
+ prog : string
+ the name of the current program (to override
+ os.path.basename(sys.argv[0])).
+ epilog : string
+ paragraph of help text to print after option help
+
+ option_groups : [OptionGroup]
+ list of option groups in this parser (option groups are
+ irrelevant for parsing the command-line, but very useful
+ for generating help)
+
+ allow_interspersed_args : bool = true
+ if true, positional arguments may be interspersed with options.
+ Assuming -a and -b each take a single argument, the command-line
+ -ablah foo bar -bboo baz
+ will be interpreted the same as
+ -ablah -bboo -- foo bar baz
+ If this flag were false, that command line would be interpreted as
+ -ablah -- foo bar -bboo baz
+ -- ie. we stop processing options as soon as we see the first
+ non-option argument. (This is the tradition followed by
+ Python's getopt module, Perl's Getopt::Std, and other argument-
+ parsing libraries, but it is generally annoying to users.)
+
+ process_default_values : bool = true
+ if true, option default values are processed similarly to option
+ values from the command line: that is, they are passed to the
+ type-checking function for the option's type (as long as the
+ default value is a string). (This really only matters if you
+ have defined custom types; see SF bug #955889.) Set it to false
+ to restore the behaviour of Optik 1.4.1 and earlier.
+
+ rargs : [string]
+ the argument list currently being parsed. Only set when
+ parse_args() is active, and continually trimmed down as
+ we consume arguments. Mainly there for the benefit of
+ callback options.
+ largs : [string]
+ the list of leftover arguments that we have skipped while
+ parsing options. If allow_interspersed_args is false, this
+ list is always empty.
+ values : Values
+ the set of option values currently being accumulated. Only
+ set when parse_args() is active. Also mainly for callbacks.
+
+ Because of the 'rargs', 'largs', and 'values' attributes,
+ OptionParser is not thread-safe. If, for some perverse reason, you
+ need to parse command-line arguments simultaneously in different
+ threads, use different OptionParser instances.
+
+ """
+
+ standard_option_list = []
+
+ def __init__(self,
+ usage=None,
+ option_list=None,
+ option_class=Option,
+ version=None,
+ conflict_handler="error",
+ description=None,
+ formatter=None,
+ add_help_option=True,
+ prog=None,
+ epilog=None):
+ OptionContainer.__init__(
+ self, option_class, conflict_handler, description)
+ self.set_usage(usage)
+ self.prog = prog
+ self.version = version
+ self.allow_interspersed_args = True
+ self.process_default_values = True
+ if formatter is None:
+ formatter = IndentedHelpFormatter()
+ self.formatter = formatter
+ self.formatter.set_parser(self)
+ self.epilog = epilog
+
+ # Populate the option list; initial sources are the
+ # standard_option_list class attribute, the 'option_list'
+ # argument, and (if applicable) the _add_version_option() and
+ # _add_help_option() methods.
+ self._populate_option_list(option_list,
+ add_help=add_help_option)
+
+ self._init_parsing_state()
+
+
+ def destroy(self):
+ """
+ Declare that you are done with this OptionParser. This cleans up
+ reference cycles so the OptionParser (and all objects referenced by
+ it) can be garbage-collected promptly. After calling destroy(), the
+ OptionParser is unusable.
+ """
+ OptionContainer.destroy(self)
+ for group in self.option_groups:
+ group.destroy()
+ del self.option_list
+ del self.option_groups
+ del self.formatter
+
+
+ # -- Private methods -----------------------------------------------
+ # (used by our or OptionContainer's constructor)
+
+ def _create_option_list(self):
+ self.option_list = []
+ self.option_groups = []
+ self._create_option_mappings()
+
+ def _add_help_option(self):
+ self.add_option("-h", "--help",
+ action="help",
+ help=_("show this help message and exit"))
+
+ def _add_version_option(self):
+ self.add_option("--version",
+ action="version",
+ help=_("show program's version number and exit"))
+
+ def _populate_option_list(self, option_list, add_help=True):
+ if self.standard_option_list:
+ self.add_options(self.standard_option_list)
+ if option_list:
+ self.add_options(option_list)
+ if self.version:
+ self._add_version_option()
+ if add_help:
+ self._add_help_option()
+
+ def _init_parsing_state(self):
+ # These are set in parse_args() for the convenience of callbacks.
+ self.rargs = None
+ self.largs = None
+ self.values = None
+
+
+ # -- Simple modifier methods ---------------------------------------
+
+ def set_usage(self, usage):
+ if usage is None:
+ self.usage = _("%prog [options]")
+ elif usage is SUPPRESS_USAGE:
+ self.usage = None
+ # For backwards compatibility with Optik 1.3 and earlier.
+ elif string.lower(usage)[:7] == "usage: ":
+ self.usage = usage[7:]
+ else:
+ self.usage = usage
+
+ def enable_interspersed_args(self):
+ self.allow_interspersed_args = True
+
+ def disable_interspersed_args(self):
+ self.allow_interspersed_args = False
+
+ def set_process_default_values(self, process):
+ self.process_default_values = process
+
+ def set_default(self, dest, value):
+ self.defaults[dest] = value
+
+ def set_defaults(self, **kwargs):
+ self.defaults.update(kwargs)
+
+ def _get_all_options(self):
+ options = self.option_list[:]
+ for group in self.option_groups:
+ options.extend(group.option_list)
+ return options
+
+ def get_default_values(self):
+ if not self.process_default_values:
+ # Old, pre-Optik 1.5 behaviour.
+ return Values(self.defaults)
+
+ defaults = self.defaults.copy()
+ for option in self._get_all_options():
+ default = defaults.get(option.dest)
+ if isbasestring(default):
+ opt_str = option.get_opt_string()
+ defaults[option.dest] = option.check_value(opt_str, default)
+
+ return Values(defaults)
+
+
+ # -- OptionGroup methods -------------------------------------------
+
+ def add_option_group(self, *args, **kwargs):
+ # XXX lots of overlap with OptionContainer.add_option()
+ if type(args[0]) is types.StringType:
+ group = apply(OptionGroup, (self,) + args, kwargs)
+ elif len(args) == 1 and not kwargs:
+ group = args[0]
+ if not isinstance(group, OptionGroup):
+ raise TypeError, "not an OptionGroup instance: %r" % group
+ if group.parser is not self:
+ raise ValueError, "invalid OptionGroup (wrong parser)"
+ else:
+ raise TypeError, "invalid arguments"
+
+ self.option_groups.append(group)
+ return group
+
+ def get_option_group(self, opt_str):
+ option = (self._short_opt.get(opt_str) or
+ self._long_opt.get(opt_str))
+ if option and option.container is not self:
+ return option.container
+ return None
+
+
+ # -- Option-parsing methods ----------------------------------------
+
+ def _get_args(self, args):
+ if args is None:
+ return sys.argv[1:]
+ else:
+ return args[:] # don't modify caller's list
+
+ def parse_args(self, args=None, values=None):
+ """
+ parse_args(args : [string] = sys.argv[1:],
+ values : Values = None)
+ -> (values : Values, args : [string])
+
+ Parse the command-line options found in 'args' (default:
+ sys.argv[1:]). Any errors result in a call to 'error()', which
+ by default prints the usage message to stderr and calls
+ sys.exit() with an error message. On success returns a pair
+ (values, args) where 'values' is an Values instance (with all
+ your option values) and 'args' is the list of arguments left
+ over after parsing options.
+ """
+ rargs = self._get_args(args)
+ if values is None:
+ values = self.get_default_values()
+
+ # Store the halves of the argument list as attributes for the
+ # convenience of callbacks:
+ # rargs
+ # the rest of the command-line (the "r" stands for
+ # "remaining" or "right-hand")
+ # largs
+ # the leftover arguments -- ie. what's left after removing
+ # options and their arguments (the "l" stands for "leftover"
+ # or "left-hand")
+ self.rargs = rargs
+ self.largs = largs = []
+ self.values = values
+
+ try:
+ stop = self._process_args(largs, rargs, values)
+ except (BadOptionError, OptionValueError), err:
+ self.error(str(err))
+
+ args = largs + rargs
+ return self.check_values(values, args)
+
+ def check_values(self, values, args):
+ """
+ check_values(values : Values, args : [string])
+ -> (values : Values, args : [string])
+
+ Check that the supplied option values and leftover arguments are
+ valid. Returns the option values and leftover arguments
+ (possibly adjusted, possibly completely new -- whatever you
+ like). Default implementation just returns the passed-in
+ values; subclasses may override as desired.
+ """
+ return (values, args)
+
+ def _process_args(self, largs, rargs, values):
+ """_process_args(largs : [string],
+ rargs : [string],
+ values : Values)
+
+ Process command-line arguments and populate 'values', consuming
+ options and arguments from 'rargs'. If 'allow_interspersed_args' is
+ false, stop at the first non-option argument. If true, accumulate any
+ interspersed non-option arguments in 'largs'.
+ """
+ while rargs:
+ arg = rargs[0]
+ # We handle bare "--" explicitly, and bare "-" is handled by the
+ # standard arg handler since the short arg case ensures that the
+ # len of the opt string is greater than 1.
+ if arg == "--":
+ del rargs[0]
+ return
+ elif arg[0:2] == "--":
+ # process a single long option (possibly with value(s))
+ self._process_long_opt(rargs, values)
+ elif arg[:1] == "-" and len(arg) > 1:
+ # process a cluster of short options (possibly with
+ # value(s) for the last one only)
+ self._process_short_opts(rargs, values)
+ elif self.allow_interspersed_args:
+ largs.append(arg)
+ del rargs[0]
+ else:
+ return # stop now, leave this arg in rargs
+
+ # Say this is the original argument list:
+ # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)]
+ # ^
+ # (we are about to process arg(i)).
+ #
+ # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of
+ # [arg0, ..., arg(i-1)] (any options and their arguments will have
+ # been removed from largs).
+ #
+ # The while loop will usually consume 1 or more arguments per pass.
+ # If it consumes 1 (eg. arg is an option that takes no arguments),
+ # then after _process_arg() is done the situation is:
+ #
+ # largs = subset of [arg0, ..., arg(i)]
+ # rargs = [arg(i+1), ..., arg(N-1)]
+ #
+ # If allow_interspersed_args is false, largs will always be
+ # *empty* -- still a subset of [arg0, ..., arg(i-1)], but
+ # not a very interesting subset!
+
+ def _match_long_opt(self, opt):
+ """_match_long_opt(opt : string) -> string
+
+ Determine which long option string 'opt' matches, ie. which one
+ it is an unambiguous abbrevation for. Raises BadOptionError if
+ 'opt' doesn't unambiguously match any long option string.
+ """
+ return _match_abbrev(opt, self._long_opt)
+
+ def _process_long_opt(self, rargs, values):
+ arg = rargs.pop(0)
+
+ # Value explicitly attached to arg? Pretend it's the next
+ # argument.
+ if "=" in arg:
+ (opt, next_arg) = string.split(arg, "=", 1)
+ rargs.insert(0, next_arg)
+ had_explicit_value = True
+ else:
+ opt = arg
+ had_explicit_value = False
+
+ opt = self._match_long_opt(opt)
+ option = self._long_opt[opt]
+ if option.takes_value():
+ nargs = option.nargs
+ if len(rargs) < nargs:
+ if nargs == 1:
+ self.error(_("%s option requires an argument") % opt)
+ else:
+ self.error(_("%s option requires %d arguments")
+ % (opt, nargs))
+ elif nargs == 1:
+ value = rargs.pop(0)
+ else:
+ value = tuple(rargs[0:nargs])
+ del rargs[0:nargs]
+
+ elif had_explicit_value:
+ self.error(_("%s option does not take a value") % opt)
+
+ else:
+ value = None
+
+ option.process(opt, value, values, self)
+
+ def _process_short_opts(self, rargs, values):
+ arg = rargs.pop(0)
+ stop = False
+ i = 1
+ for ch in arg[1:]:
+ opt = "-" + ch
+ option = self._short_opt.get(opt)
+ i = i + 1 # we have consumed a character
+
+ if not option:
+ raise BadOptionError(opt)
+ if option.takes_value():
+ # Any characters left in arg? Pretend they're the
+ # next arg, and stop consuming characters of arg.
+ if i < len(arg):
+ rargs.insert(0, arg[i:])
+ stop = True
+
+ nargs = option.nargs
+ if len(rargs) < nargs:
+ if nargs == 1:
+ self.error(_("%s option requires an argument") % opt)
+ else:
+ self.error(_("%s option requires %d arguments")
+ % (opt, nargs))
+ elif nargs == 1:
+ value = rargs.pop(0)
+ else:
+ value = tuple(rargs[0:nargs])
+ del rargs[0:nargs]
+
+ else: # option doesn't take a value
+ value = None
+
+ option.process(opt, value, values, self)
+
+ if stop:
+ break
+
+
+ # -- Feedback methods ----------------------------------------------
+
+ def get_prog_name(self):
+ if self.prog is None:
+ return os.path.basename(sys.argv[0])
+ else:
+ return self.prog
+
+ def expand_prog_name(self, s):
+ return string.replace(s, "%prog", self.get_prog_name())
+
+ def get_description(self):
+ return self.expand_prog_name(self.description)
+
+ def exit(self, status=0, msg=None):
+ if msg:
+ sys.stderr.write(msg)
+ sys.exit(status)
+
+ def error(self, msg):
+ """error(msg : string)
+
+ Print a usage message incorporating 'msg' to stderr and exit.
+ If you override this in a subclass, it should not return -- it
+ should either exit or raise an exception.
+ """
+ self.print_usage(sys.stderr)
+ self.exit(2, "%s: error: %s\n" % (self.get_prog_name(), msg))
+
+ def get_usage(self):
+ if self.usage:
+ return self.formatter.format_usage(
+ self.expand_prog_name(self.usage))
+ else:
+ return ""
+
+ def print_usage(self, file=None):
+ """print_usage(file : file = stdout)
+
+ Print the usage message for the current program (self.usage) to
+ 'file' (default stdout). Any occurence of the string "%prog" in
+ self.usage is replaced with the name of the current program
+ (basename of sys.argv[0]). Does nothing if self.usage is empty
+ or not defined.
+ """
+ if self.usage:
+ file.write(self.get_usage() + '\n')
+
+ def get_version(self):
+ if self.version:
+ return self.expand_prog_name(self.version)
+ else:
+ return ""
+
+ def print_version(self, file=None):
+ """print_version(file : file = stdout)
+
+ Print the version message for this program (self.version) to
+ 'file' (default stdout). As with print_usage(), any occurence
+ of "%prog" in self.version is replaced by the current program's
+ name. Does nothing if self.version is empty or undefined.
+ """
+ if self.version:
+ file.write(self.get_version() + '\n')
+
+ def format_option_help(self, formatter=None):
+ if formatter is None:
+ formatter = self.formatter
+ formatter.store_option_strings(self)
+ result = []
+ result.append(formatter.format_heading(_("Options")))
+ formatter.indent()
+ if self.option_list:
+ result.append(OptionContainer.format_option_help(self, formatter))
+ result.append("\n")
+ for group in self.option_groups:
+ result.append(group.format_help(formatter))
+ result.append("\n")
+ formatter.dedent()
+ # Drop the last "\n", or the header if no options or option groups:
+ return string.join(result[:-1], "")
+
+ def format_epilog(self, formatter):
+ return formatter.format_epilog(self.epilog)
+
+ def format_help(self, formatter=None):
+ if formatter is None:
+ formatter = self.formatter
+ result = []
+ if self.usage:
+ result.append(self.get_usage() + "\n")
+ if self.description:
+ result.append(self.format_description(formatter) + "\n")
+ result.append(self.format_option_help(formatter))
+ result.append(self.format_epilog(formatter))
+ return string.join(result, "")
+
+ # used by test suite
+ def _get_encoding(self, file):
+ encoding = getattr(file, "encoding", None)
+ if not encoding:
+ encoding = sys.getdefaultencoding()
+ return encoding
+
+ def print_help(self, file=None):
+ """print_help(file : file = stdout)
+
+ Print an extended help message, listing all options and any
+ help text provided with them, to 'file' (default stdout).
+ """
+ if file is None:
+ file = sys.stdout
+ encoding = self._get_encoding(file)
+ file.write(encode_wrapper(self.format_help(), encoding, "replace"))
+
+# class OptionParser
+
+
+def _match_abbrev(s, wordmap):
+ """_match_abbrev(s : string, wordmap : {string : Option}) -> string
+
+ Return the string key in 'wordmap' for which 's' is an unambiguous
+ abbreviation. If 's' is found to be ambiguous or doesn't match any of
+ 'words', raise BadOptionError.
+ """
+ # Is there an exact match?
+ if wordmap.has_key(s):
+ return s
+ else:
+ # Isolate all words with s as a prefix.
+ possibilities = filter(lambda w, s=s: w[:len(s)] == s, wordmap.keys())
+ # No exact match, so there had better be just one possibility.
+ if len(possibilities) == 1:
+ return possibilities[0]
+ elif not possibilities:
+ raise BadOptionError(s)
+ else:
+ # More than one possible completion: ambiguous prefix.
+ possibilities.sort()
+ raise AmbiguousOptionError(s, possibilities)
+
+
+# Some day, there might be many Option classes. As of Optik 1.3, the
+# preferred way to instantiate Options is indirectly, via make_option(),
+# which will become a factory function when there are many Option
+# classes.
+make_option = Option
diff --git a/src/engine/SCons/compat/_scons_textwrap.py b/src/engine/SCons/compat/_scons_textwrap.py
new file mode 100644
index 0000000..1e0e411
--- /dev/null
+++ b/src/engine/SCons/compat/_scons_textwrap.py
@@ -0,0 +1,373 @@
+"""Text wrapping and filling.
+"""
+
+# Copyright (C) 1999-2001 Gregory P. Ward.
+# Copyright (C) 2002, 2003 Python Software Foundation.
+# Written by Greg Ward <gward@python.net>
+
+__revision__ = "$Id: textwrap.py,v 1.32.8.2 2004/05/13 01:48:15 gward Exp $"
+
+import string, re
+
+try:
+ unicode
+except NameError:
+ class unicode:
+ pass
+
+# Do the right thing with boolean values for all known Python versions
+# (so this module can be copied to projects that don't depend on Python
+# 2.3, e.g. Optik and Docutils).
+try:
+ True, False
+except NameError:
+ (True, False) = (1, 0)
+
+__all__ = ['TextWrapper', 'wrap', 'fill']
+
+# Hardcode the recognized whitespace characters to the US-ASCII
+# whitespace characters. The main reason for doing this is that in
+# ISO-8859-1, 0xa0 is non-breaking whitespace, so in certain locales
+# that character winds up in string.whitespace. Respecting
+# string.whitespace in those cases would 1) make textwrap treat 0xa0 the
+# same as any other whitespace char, which is clearly wrong (it's a
+# *non-breaking* space), 2) possibly cause problems with Unicode,
+# since 0xa0 is not in range(128).
+_whitespace = '\t\n\x0b\x0c\r '
+
+class TextWrapper:
+ """
+ Object for wrapping/filling text. The public interface consists of
+ the wrap() and fill() methods; the other methods are just there for
+ subclasses to override in order to tweak the default behaviour.
+ If you want to completely replace the main wrapping algorithm,
+ you'll probably have to override _wrap_chunks().
+
+ Several instance attributes control various aspects of wrapping:
+ width (default: 70)
+ the maximum width of wrapped lines (unless break_long_words
+ is false)
+ initial_indent (default: "")
+ string that will be prepended to the first line of wrapped
+ output. Counts towards the line's width.
+ subsequent_indent (default: "")
+ string that will be prepended to all lines save the first
+ of wrapped output; also counts towards each line's width.
+ expand_tabs (default: true)
+ Expand tabs in input text to spaces before further processing.
+ Each tab will become 1 .. 8 spaces, depending on its position in
+ its line. If false, each tab is treated as a single character.
+ replace_whitespace (default: true)
+ Replace all whitespace characters in the input text by spaces
+ after tab expansion. Note that if expand_tabs is false and
+ replace_whitespace is true, every tab will be converted to a
+ single space!
+ fix_sentence_endings (default: false)
+ Ensure that sentence-ending punctuation is always followed
+ by two spaces. Off by default because the algorithm is
+ (unavoidably) imperfect.
+ break_long_words (default: true)
+ Break words longer than 'width'. If false, those words will not
+ be broken, and some lines might be longer than 'width'.
+ """
+
+ whitespace_trans = string.maketrans(_whitespace, ' ' * len(_whitespace))
+
+ unicode_whitespace_trans = {}
+ try:
+ uspace = eval("ord(u' ')")
+ except SyntaxError:
+ # Python1.5 doesn't understand u'' syntax, in which case we
+ # won't actually use the unicode translation below, so it
+ # doesn't matter what value we put in the table.
+ uspace = ord(' ')
+ for x in map(ord, _whitespace):
+ unicode_whitespace_trans[x] = uspace
+
+ # This funky little regex is just the trick for splitting
+ # text up into word-wrappable chunks. E.g.
+ # "Hello there -- you goof-ball, use the -b option!"
+ # splits into
+ # Hello/ /there/ /--/ /you/ /goof-/ball,/ /use/ /the/ /-b/ /option!
+ # (after stripping out empty strings).
+ wordsep_re = re.compile(r'(\s+|' # any whitespace
+ r'-*\w{2,}-(?=\w{2,}))') # hyphenated words
+ # Earlier Python's don't have the (?<=
+ # negative look-behind assertion. It doesn't
+ # matter for the simple input SCons is going to
+ # give it, so just comment it out.
+ #r'(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))') # em-dash
+
+ # XXX will there be a locale-or-charset-aware version of
+ # string.lowercase in 2.3?
+ sentence_end_re = re.compile(r'[%s]' # lowercase letter
+ r'[\.\!\?]' # sentence-ending punct.
+ r'[\"\']?' # optional end-of-quote
+ % string.lowercase)
+
+
+ def __init__(self,
+ width=70,
+ initial_indent="",
+ subsequent_indent="",
+ expand_tabs=True,
+ replace_whitespace=True,
+ fix_sentence_endings=False,
+ break_long_words=True):
+ self.width = width
+ self.initial_indent = initial_indent
+ self.subsequent_indent = subsequent_indent
+ self.expand_tabs = expand_tabs
+ self.replace_whitespace = replace_whitespace
+ self.fix_sentence_endings = fix_sentence_endings
+ self.break_long_words = break_long_words
+
+
+ # -- Private methods -----------------------------------------------
+ # (possibly useful for subclasses to override)
+
+ def _munge_whitespace(self, text):
+ """_munge_whitespace(text : string) -> string
+
+ Munge whitespace in text: expand tabs and convert all other
+ whitespace characters to spaces. Eg. " foo\tbar\n\nbaz"
+ becomes " foo bar baz".
+ """
+ if self.expand_tabs:
+ text = string.expandtabs(text)
+ if self.replace_whitespace:
+ if type(text) == type(''):
+ text = string.translate(text, self.whitespace_trans)
+ elif isinstance(text, unicode):
+ text = string.translate(text, self.unicode_whitespace_trans)
+ return text
+
+
+ def _split(self, text):
+ """_split(text : string) -> [string]
+
+ Split the text to wrap into indivisible chunks. Chunks are
+ not quite the same as words; see wrap_chunks() for full
+ details. As an example, the text
+ Look, goof-ball -- use the -b option!
+ breaks into the following chunks:
+ 'Look,', ' ', 'goof-', 'ball', ' ', '--', ' ',
+ 'use', ' ', 'the', ' ', '-b', ' ', 'option!'
+ """
+ chunks = self.wordsep_re.split(text)
+ chunks = filter(None, chunks)
+ return chunks
+
+ def _fix_sentence_endings(self, chunks):
+ """_fix_sentence_endings(chunks : [string])
+
+ Correct for sentence endings buried in 'chunks'. Eg. when the
+ original text contains "... foo.\nBar ...", munge_whitespace()
+ and split() will convert that to [..., "foo.", " ", "Bar", ...]
+ which has one too few spaces; this method simply changes the one
+ space to two.
+ """
+ i = 0
+ pat = self.sentence_end_re
+ while i < len(chunks)-1:
+ if chunks[i+1] == " " and pat.search(chunks[i]):
+ chunks[i+1] = " "
+ i = i + 2
+ else:
+ i = i + 1
+
+ def _handle_long_word(self, chunks, cur_line, cur_len, width):
+ """_handle_long_word(chunks : [string],
+ cur_line : [string],
+ cur_len : int, width : int)
+
+ Handle a chunk of text (most likely a word, not whitespace) that
+ is too long to fit in any line.
+ """
+ space_left = max(width - cur_len, 1)
+
+ # If we're allowed to break long words, then do so: put as much
+ # of the next chunk onto the current line as will fit.
+ if self.break_long_words:
+ cur_line.append(chunks[0][0:space_left])
+ chunks[0] = chunks[0][space_left:]
+
+ # Otherwise, we have to preserve the long word intact. Only add
+ # it to the current line if there's nothing already there --
+ # that minimizes how much we violate the width constraint.
+ elif not cur_line:
+ cur_line.append(chunks.pop(0))
+
+ # If we're not allowed to break long words, and there's already
+ # text on the current line, do nothing. Next time through the
+ # main loop of _wrap_chunks(), we'll wind up here again, but
+ # cur_len will be zero, so the next line will be entirely
+ # devoted to the long word that we can't handle right now.
+
+ def _wrap_chunks(self, chunks):
+ """_wrap_chunks(chunks : [string]) -> [string]
+
+ Wrap a sequence of text chunks and return a list of lines of
+ length 'self.width' or less. (If 'break_long_words' is false,
+ some lines may be longer than this.) Chunks correspond roughly
+ to words and the whitespace between them: each chunk is
+ indivisible (modulo 'break_long_words'), but a line break can
+ come between any two chunks. Chunks should not have internal
+ whitespace; ie. a chunk is either all whitespace or a "word".
+ Whitespace chunks will be removed from the beginning and end of
+ lines, but apart from that whitespace is preserved.
+ """
+ lines = []
+ if self.width <= 0:
+ raise ValueError("invalid width %r (must be > 0)" % self.width)
+
+ while chunks:
+
+ # Start the list of chunks that will make up the current line.
+ # cur_len is just the length of all the chunks in cur_line.
+ cur_line = []
+ cur_len = 0
+
+ # Figure out which static string will prefix this line.
+ if lines:
+ indent = self.subsequent_indent
+ else:
+ indent = self.initial_indent
+
+ # Maximum width for this line.
+ width = self.width - len(indent)
+
+ # First chunk on line is whitespace -- drop it, unless this
+ # is the very beginning of the text (ie. no lines started yet).
+ if string.strip(chunks[0]) == '' and lines:
+ del chunks[0]
+
+ while chunks:
+ l = len(chunks[0])
+
+ # Can at least squeeze this chunk onto the current line.
+ if cur_len + l <= width:
+ cur_line.append(chunks.pop(0))
+ cur_len = cur_len + l
+
+ # Nope, this line is full.
+ else:
+ break
+
+ # The current line is full, and the next chunk is too big to
+ # fit on *any* line (not just this one).
+ if chunks and len(chunks[0]) > width:
+ self._handle_long_word(chunks, cur_line, cur_len, width)
+
+ # If the last chunk on this line is all whitespace, drop it.
+ if cur_line and string.strip(cur_line[-1]) == '':
+ del cur_line[-1]
+
+ # Convert current line back to a string and store it in list
+ # of all lines (return value).
+ if cur_line:
+ lines.append(indent + string.join(cur_line, ''))
+
+ return lines
+
+
+ # -- Public interface ----------------------------------------------
+
+ def wrap(self, text):
+ """wrap(text : string) -> [string]
+
+ Reformat the single paragraph in 'text' so it fits in lines of
+ no more than 'self.width' columns, and return a list of wrapped
+ lines. Tabs in 'text' are expanded with string.expandtabs(),
+ and all other whitespace characters (including newline) are
+ converted to space.
+ """
+ text = self._munge_whitespace(text)
+ indent = self.initial_indent
+ chunks = self._split(text)
+ if self.fix_sentence_endings:
+ self._fix_sentence_endings(chunks)
+ return self._wrap_chunks(chunks)
+
+ def fill(self, text):
+ """fill(text : string) -> string
+
+ Reformat the single paragraph in 'text' to fit in lines of no
+ more than 'self.width' columns, and return a new string
+ containing the entire wrapped paragraph.
+ """
+ return string.join(self.wrap(text), "\n")
+
+
+# -- Convenience interface ---------------------------------------------
+
+def wrap(text, width=70, **kwargs):
+ """Wrap a single paragraph of text, returning a list of wrapped lines.
+
+ Reformat the single paragraph in 'text' so it fits in lines of no
+ more than 'width' columns, and return a list of wrapped lines. By
+ default, tabs in 'text' are expanded with string.expandtabs(), and
+ all other whitespace characters (including newline) are converted to
+ space. See TextWrapper class for available keyword args to customize
+ wrapping behaviour.
+ """
+ kw = kwargs.copy()
+ kw['width'] = width
+ w = apply(TextWrapper, (), kw)
+ return w.wrap(text)
+
+def fill(text, width=70, **kwargs):
+ """Fill a single paragraph of text, returning a new string.
+
+ Reformat the single paragraph in 'text' to fit in lines of no more
+ than 'width' columns, and return a new string containing the entire
+ wrapped paragraph. As with wrap(), tabs are expanded and other
+ whitespace characters converted to space. See TextWrapper class for
+ available keyword args to customize wrapping behaviour.
+ """
+ kw = kwargs.copy()
+ kw['width'] = width
+ w = apply(TextWrapper, (), kw)
+ return w.fill(text)
+
+
+# -- Loosely related functionality -------------------------------------
+
+def dedent(text):
+ """dedent(text : string) -> string
+
+ Remove any whitespace than can be uniformly removed from the left
+ of every line in `text`.
+
+ This can be used e.g. to make triple-quoted strings line up with
+ the left edge of screen/whatever, while still presenting it in the
+ source code in indented form.
+
+ For example:
+
+ def test():
+ # end first line with \ to avoid the empty line!
+ s = '''\
+ hello
+ world
+ '''
+ print repr(s) # prints ' hello\n world\n '
+ print repr(dedent(s)) # prints 'hello\n world\n'
+ """
+ lines = text.expandtabs().split('\n')
+ margin = None
+ for line in lines:
+ content = line.lstrip()
+ if not content:
+ continue
+ indent = len(line) - len(content)
+ if margin is None:
+ margin = indent
+ else:
+ margin = min(margin, indent)
+
+ if margin is not None and margin > 0:
+ for i in range(len(lines)):
+ lines[i] = lines[i][margin:]
+
+ return string.join(lines, '\n')
diff --git a/src/engine/SCons/compat/builtins.py b/src/engine/SCons/compat/builtins.py
index 1124cf0..a4c3080 100644
--- a/src/engine/SCons/compat/builtins.py
+++ b/src/engine/SCons/compat/builtins.py
@@ -35,6 +35,8 @@ the earliest ones we support.
This module checks for the following __builtin__ names:
+ all()
+ any()
bool()
dict()
True
@@ -58,6 +60,36 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import __builtin__
try:
+ all
+except NameError:
+ # Pre-2.5 Python has no all() function.
+ def all(iterable):
+ """
+ Returns True if all elements of the iterable are true.
+ """
+ for element in iterable:
+ if not element:
+ return False
+ return True
+ __builtin__.all = all
+ all = all
+
+try:
+ any
+except NameError:
+ # Pre-2.5 Python has no any() function.
+ def any(iterable):
+ """
+ Returns True if any element of the iterable is true.
+ """
+ for element in iterable:
+ if element:
+ return True
+ return False
+ __builtin__.any = any
+ any = any
+
+try:
bool
except NameError:
# Pre-2.2 Python has no bool() function.
diff --git a/src/script/scons-time.py b/src/script/scons-time.py
index b0feaab..284443a 100644
--- a/src/script/scons-time.py
+++ b/src/script/scons-time.py
@@ -31,6 +31,8 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
+from __future__ import nested_scopes
+
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import getopt
@@ -43,6 +45,34 @@ import sys
import tempfile
import time
+try:
+ False
+except NameError:
+ # Pre-2.2 Python has no False keyword.
+ import __builtin__
+ __builtin__.False = not 1
+
+try:
+ True
+except NameError:
+ # Pre-2.2 Python has no True keyword.
+ import __builtin__
+ __builtin__.True = not 0
+
+def make_temp_file(**kw):
+ try:
+ result = tempfile.mktemp(**kw)
+ except TypeError:
+ try:
+ save_template = tempfile.template
+ prefix = kw['prefix']
+ del kw['prefix']
+ tempfile.template = prefix
+ result = tempfile.mktemp(**kw)
+ finally:
+ tempfile.template = save_template
+ return result
+
class Plotter:
def increment_size(self, largest):
"""
@@ -751,13 +781,13 @@ class SConsTimer:
elif o in ('-?', '-h', '--help'):
self.do_help(['help', 'func'])
sys.exit(0)
- elif o in ('--max'):
+ elif o in ('--max',):
max_time = int(a)
elif o in ('-p', '--prefix'):
self.prefix = a
elif o in ('-t', '--tail'):
tail = int(a)
- elif o in ('--title'):
+ elif o in ('--title',):
self.title = a
if self.config_file:
@@ -870,14 +900,14 @@ class SConsTimer:
sys.exit(0)
elif o in ('-p', '--prefix'):
self.prefix = a
- elif o in ('--stage'):
+ elif o in ('--stage',):
if not a in self.stages:
sys.stderr.write('%s: mem: Unrecognized stage "%s".\n' % (self.name, a))
sys.exit(1)
stage = a
elif o in ('-t', '--tail'):
tail = int(a)
- elif o in ('--title'):
+ elif o in ('--title',):
self.title = a
if self.config_file:
@@ -982,7 +1012,7 @@ class SConsTimer:
sys.exit(0)
elif o in ('-p', '--prefix'):
self.prefix = a
- elif o in ('--stage'):
+ elif o in ('--stage',):
if not a in self.stages:
sys.stderr.write('%s: obj: Unrecognized stage "%s".\n' % (self.name, a))
sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name))
@@ -990,7 +1020,7 @@ class SConsTimer:
stage = a
elif o in ('-t', '--tail'):
tail = int(a)
- elif o in ('--title'):
+ elif o in ('--title',):
self.title = a
if not args:
@@ -1103,7 +1133,7 @@ class SConsTimer:
opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
for o, a in opts:
- if o in ('--aegis'):
+ if o in ('--aegis',):
self.aegis_project = a
elif o in ('-f', '--file'):
self.config_file = a
@@ -1112,19 +1142,19 @@ class SConsTimer:
sys.exit(0)
elif o in ('-n', '--no-exec'):
self.execute = self._do_not_execute
- elif o in ('--number'):
+ elif o in ('--number',):
run_number_list = self.split_run_numbers(a)
- elif o in ('--outdir'):
+ elif o in ('--outdir',):
self.outdir = a
elif o in ('-p', '--prefix'):
self.prefix = a
- elif o in ('--python'):
+ elif o in ('--python',):
self.python = a
elif o in ('-q', '--quiet'):
self.display = self._do_not_display
elif o in ('-s', '--subdir'):
self.subdir = a
- elif o in ('--scons'):
+ elif o in ('--scons',):
self.scons = a
elif o in ('--svn', '--subversion'):
self.subversion_url = a
@@ -1179,7 +1209,7 @@ class SConsTimer:
return os.path.join(dir, 'src', 'engine')
def prep_aegis_run(self, commands, removals):
- self.aegis_tmpdir = tempfile.mktemp(prefix = self.name + '-aegis-')
+ self.aegis_tmpdir = make_temp_file(prefix = self.name + '-aegis-')
removals.append((shutil.rmtree, 'rm -rf %%s', self.aegis_tmpdir))
self.aegis_parent_project = os.path.splitext(self.aegis_project)[0]
@@ -1194,7 +1224,7 @@ class SConsTimer:
])
def prep_subversion_run(self, commands, removals):
- self.svn_tmpdir = tempfile.mktemp(prefix = self.name + '-svn-')
+ self.svn_tmpdir = make_temp_file(prefix = self.name + '-svn-')
removals.append((shutil.rmtree, 'rm -rf %%s', self.svn_tmpdir))
self.scons = self.scons_path(self.svn_tmpdir)
@@ -1248,7 +1278,7 @@ class SConsTimer:
if self.targets2 is None:
self.targets2 = self.targets
- self.tmpdir = tempfile.mktemp(prefix = self.name + '-')
+ self.tmpdir = make_temp_file(prefix = self.name + '-')
commands.extend([
'mkdir %(tmpdir)s',
@@ -1364,9 +1394,9 @@ class SConsTimer:
self.prefix = a
elif o in ('-t', '--tail'):
tail = int(a)
- elif o in ('--title'):
+ elif o in ('--title',):
self.title = a
- elif o in ('--which'):
+ elif o in ('--which',):
if not a in self.time_strings.keys():
sys.stderr.write('%s: time: Unrecognized timer "%s".\n' % (self.name, a))
sys.stderr.write('%s Type "%s help time" for help.\n' % (self.name_spaces, self.name))
diff --git a/test/AS/AS.py b/test/AS/AS.py
index 8bfa740..e8a2a59 100644
--- a/test/AS/AS.py
+++ b/test/AS/AS.py
@@ -24,9 +24,13 @@
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-import os
-import string
+"""
+Verify the ability to set the $AS construction variable to a different
+assembler (a wrapper we create).
+"""
+
import sys
+
import TestSCons
_python_ = TestSCons._python_
@@ -153,314 +157,17 @@ test.write('test6.SPP', r"""This is a .SPP file.
test.run(arguments = '.', stderr = None)
-test.fail_test(test.read('test1' + _exe) != "This is a .s file.\n")
-
-test.fail_test(test.read('test2' + _exe) != "This is a .S file.\n")
-
-test.fail_test(test.read('test3' + _exe) != "This is a .asm file.\n")
-
-test.fail_test(test.read('test4' + _exe) != "This is a .ASM file.\n")
-
-test.fail_test(test.read('test5' + _exe) != "This is a .spp file.\n")
-
-test.fail_test(test.read('test6' + _exe) != "This is a .SPP file.\n")
-
-
-as = test.detect('AS', 'as')
-x86 = (sys.platform == 'win32' or string.find(sys.platform, 'linux') != -1)
-
-if as and x86:
-
- test.write("wrapper.py", """\
-import os
-import string
-import sys
-open('%s', 'wb').write("wrapper.py: %%s\\n" %% sys.argv[-1])
-cmd = string.join(sys.argv[1:])
-os.system(cmd)
-""" % string.replace(test.workpath('wrapper.out'), '\\', '\\\\'))
-
- test.write('SConstruct', """\
-aaa = Environment()
-bbb = aaa.Clone(AS = r'%(_python_)s wrapper.py ' + WhereIs('as'))
-ccc = aaa.Clone(CPPPATH=['.'])
-aaa.Program(target = 'aaa', source = ['aaa.s', 'aaa_main.c'])
-bbb.Program(target = 'bbb', source = ['bbb.s', 'bbb_main.c'])
-ccc.Program(target = 'ccc', source = ['ccc.S', 'ccc_main.c'])
-""" % locals())
-
- test.write('aaa.s',
-""" .file "aaa.s"
-.data
-.align 4
-.globl name
-name:
- .ascii "aaa.s"
- .byte 0
-""")
-
- test.write('bbb.s', """\
-.file "bbb.s"
-.data
-.align 4
-.globl name
-name:
- .ascii "bbb.s"
- .byte 0
-""")
-
- test.write('ccc.h', """\
-#define STRING "ccc.S"
-""")
-
- test.write('ccc.S', """\
-#include <ccc.h>
-.file STRING
-.data
-.align 4
-.globl name
-name:
- .ascii STRING
- .byte 0
-""")
-
- test.write('aaa_main.c', r"""
-#include <stdlib.h>
-#include <stdio.h>
-
-extern char name[];
-
-int
-main(int argc, char *argv[])
-{
- argv[argc++] = "--";
- printf("aaa_main.c %s\n", name);
- exit (0);
-}
-""")
-
- test.write('bbb_main.c', r"""
-#include <stdlib.h>
-#include <stdio.h>
-
-extern char name[];
-
-int
-main(int argc, char *argv[])
-{
- argv[argc++] = "--";
- printf("bbb_main.c %s\n", name);
- exit (0);
-}
-""")
-
- test.write('ccc_main.c', r"""
-#include <stdlib.h>
-#include <stdio.h>
-
-extern char name[];
-
-int
-main(int argc, char *argv[])
-{
- argv[argc++] = "--";
- printf("ccc_main.c %s\n", name);
- exit (0);
-}
-""")
-
- test.write('ddd_main.c', r"""
-extern char name[];
-
-int
-main(int argc, char *argv[])
-{
- argv[argc++] = "--";
- printf("ddd_main.c %s\n", name);
- exit (0);
-}
-""")
-
- test.run()
-
- test.run(program = test.workpath('aaa'), stdout = "aaa_main.c aaa.s\n")
- test.run(program = test.workpath('bbb'), stdout = "bbb_main.c bbb.s\n")
- test.run(program = test.workpath('ccc'), stdout = "ccc_main.c ccc.S\n")
-
- test.fail_test(test.read('wrapper.out') != "wrapper.py: bbb.s\n")
-
- test.write("ccc.h", """\
-#define STRING "ccc.S 2"
-""")
-
- test.run()
- test.run(program = test.workpath('ccc'), stdout = "ccc_main.c ccc.S 2\n")
-
- test.unlink('wrapper.out')
-
-
-
-ml = test.where_is('ml')
-
-if ml and sys.platform == 'win32':
-
- test.write("wrapper.py",
-"""import os
-import string
-import sys
-open('%s', 'wb').write("wrapper.py\\n")
-os.system(string.join(sys.argv[1:], " "))
-""" % string.replace(test.workpath('wrapper.out'), '\\', '\\\\'))
-
- test.write('SConstruct', """
-import os
-ccc = Environment(tools = ['msvc', 'mslink', 'masm'],
- ASFLAGS = '/nologo /coff')
-ccc['ENV']['PATH'] = ccc['ENV']['PATH'] + os.pathsep + os.environ['PATH']
-ddd = ccc.Clone(AS = r'%(_python_)s wrapper.py ' + ccc['AS'])
-ccc.Program(target = 'ccc', source = ['ccc.asm', 'ccc_main.c'])
-ddd.Program(target = 'ddd', source = ['ddd.asm', 'ddd_main.c'])
-""" % locals())
-
- test.write('ccc.asm',
-"""
-DSEG segment
- PUBLIC _name
-_name byte "ccc.asm",0
-DSEG ends
- end
-""")
-
- test.write('ddd.asm',
-"""
-DSEG segment
- PUBLIC _name
-_name byte "ddd.asm",0
-DSEG ends
- end
-""")
-
- test.write('ccc_main.c', r"""
-extern char name[];
-
-int
-main(int argc, char *argv[])
-{
- argv[argc++] = "--";
- printf("ccc_main.c %s\n", name);
- exit (0);
-}
-""")
-
- test.write('ddd_main.c', r"""
-extern char name[];
-
-int
-main(int argc, char *argv[])
-{
- argv[argc++] = "--";
- printf("ddd_main.c %s\n", name);
- exit (0);
-}
-""")
-
- test.run(arguments = 'ccc' + _exe, stderr = None)
-
- test.run(program = test.workpath('ccc'), stdout = "ccc_main.c ccc.asm\n")
-
- test.fail_test(os.path.exists(test.workpath('wrapper.out')))
-
- test.run(arguments = 'ddd' + _exe)
-
- test.run(program = test.workpath('ddd'), stdout = "ddd_main.c ddd.asm\n")
-
- test.fail_test(test.read('wrapper.out') != "wrapper.py\n")
-
-
-
-
-nasm = test.where_is('nasm')
-
-if nasm:
-
- # Allow flexibility about the type of object/executable format
- # needed on different systems. Format_map is a dict that maps
- # sys.platform substrings to the correct argument for the nasm -f
- # option. The default is "elf," which seems to be a reasonable
- # lowest common denominator (works on both Linux and FreeBSD,
- # anyway...).
- nasm_format = 'elf'
- format_map = {}
- for k, v in format_map.items():
- if string.find(sys.platform, k) != -1:
- nasm_format = v
- break
-
- test.write("wrapper.py",
-"""import os
-import string
-import sys
-open('%s', 'wb').write("wrapper.py\\n")
-os.system(string.join(sys.argv[1:], " "))
-""" % string.replace(test.workpath('wrapper.out'), '\\', '\\\\'))
-
- test.write('SConstruct', """
-eee = Environment(tools = ['gcc', 'gnulink', 'nasm'],
- ASFLAGS = '-f %(nasm_format)s')
-fff = eee.Clone(AS = r'%(_python_)s wrapper.py ' + WhereIs('nasm'))
-eee.Program(target = 'eee', source = ['eee.asm', 'eee_main.c'])
-fff.Program(target = 'fff', source = ['fff.asm', 'fff_main.c'])
-""" % locals())
-
- test.write('eee.asm',
-"""
-global name
-name:
- db 'eee.asm',0
-""")
-
- test.write('fff.asm',
-"""
-global name
-name:
- db 'fff.asm',0
-""")
-
- test.write('eee_main.c', r"""
-extern char name[];
-
-int
-main(int argc, char *argv[])
-{
- argv[argc++] = "--";
- printf("eee_main.c %s\n", name);
- exit (0);
-}
-""")
-
- test.write('fff_main.c', r"""
-extern char name[];
-
-int
-main(int argc, char *argv[])
-{
- argv[argc++] = "--";
- printf("fff_main.c %s\n", name);
- exit (0);
-}
-""")
-
- test.run(arguments = 'eee' + _exe, stderr = None)
+test.must_match('test1' + _exe, "This is a .s file.\n")
- test.run(program = test.workpath('eee'), stdout = "eee_main.c eee.asm\n")
+test.must_match('test2' + _exe, "This is a .S file.\n")
- test.fail_test(os.path.exists(test.workpath('wrapper.out')))
+test.must_match('test3' + _exe, "This is a .asm file.\n")
- test.run(arguments = 'fff' + _exe)
+test.must_match('test4' + _exe, "This is a .ASM file.\n")
- test.run(program = test.workpath('fff'), stdout = "fff_main.c fff.asm\n")
+test.must_match('test5' + _exe, "This is a .spp file.\n")
- test.fail_test(test.read('wrapper.out') != "wrapper.py\n")
+test.must_match('test6' + _exe, "This is a .SPP file.\n")
diff --git a/test/AS/as-live.py b/test/AS/as-live.py
new file mode 100644
index 0000000..886dc8d
--- /dev/null
+++ b/test/AS/as-live.py
@@ -0,0 +1,184 @@
+#!/usr/bin/env python
+#
+# __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__"
+
+"""
+Verify correct use of the live 'as' assembler.
+"""
+
+import string
+import sys
+
+import TestSCons
+
+_python_ = TestSCons._python_
+_exe = TestSCons._exe
+
+test = TestSCons.TestSCons()
+
+
+
+as = test.detect('AS', 'as')
+
+if not as:
+ test.skip_test("as not found; skipping test\n")
+
+x86 = (sys.platform == 'win32' or string.find(sys.platform, 'linux') != -1)
+
+if not x86:
+ test.skip_test("skipping as test on non-x86 platform '%s'\n" % sys.platform)
+
+
+
+test.write("wrapper.py", """\
+import os
+import string
+import sys
+open('%s', 'wb').write("wrapper.py: %%s\\n" %% sys.argv[-1])
+cmd = string.join(sys.argv[1:])
+os.system(cmd)
+""" % string.replace(test.workpath('wrapper.out'), '\\', '\\\\'))
+
+test.write('SConstruct', """\
+aaa = Environment()
+bbb = aaa.Clone(AS = r'%(_python_)s wrapper.py ' + WhereIs('as'))
+ccc = aaa.Clone(CPPPATH=['.'])
+aaa.Program(target = 'aaa', source = ['aaa.s', 'aaa_main.c'])
+bbb.Program(target = 'bbb', source = ['bbb.s', 'bbb_main.c'])
+ccc.Program(target = 'ccc', source = ['ccc.S', 'ccc_main.c'])
+""" % locals())
+
+test.write('aaa.s',
+""" .file "aaa.s"
+.data
+.align 4
+.globl name
+name:
+ .ascii "aaa.s"
+ .byte 0
+""")
+
+test.write('bbb.s', """\
+.file "bbb.s"
+.data
+.align 4
+.globl name
+name:
+ .ascii "bbb.s"
+ .byte 0
+""")
+
+test.write('ccc.h', """\
+#define STRING "ccc.S"
+""")
+
+test.write('ccc.S', """\
+#include <ccc.h>
+.file STRING
+.data
+.align 4
+.globl name
+name:
+ .ascii STRING
+ .byte 0
+""")
+
+test.write('aaa_main.c', r"""
+#include <stdlib.h>
+#include <stdio.h>
+
+extern char name[];
+
+int
+main(int argc, char *argv[])
+{
+ argv[argc++] = "--";
+ printf("aaa_main.c %s\n", name);
+ exit (0);
+}
+""")
+
+test.write('bbb_main.c', r"""
+#include <stdlib.h>
+#include <stdio.h>
+
+extern char name[];
+
+int
+main(int argc, char *argv[])
+{
+ argv[argc++] = "--";
+ printf("bbb_main.c %s\n", name);
+ exit (0);
+}
+""")
+
+test.write('ccc_main.c', r"""
+#include <stdlib.h>
+#include <stdio.h>
+
+extern char name[];
+
+int
+main(int argc, char *argv[])
+{
+ argv[argc++] = "--";
+ printf("ccc_main.c %s\n", name);
+ exit (0);
+}
+""")
+
+test.write('ddd_main.c', r"""
+extern char name[];
+
+int
+main(int argc, char *argv[])
+{
+ argv[argc++] = "--";
+ printf("ddd_main.c %s\n", name);
+ exit (0);
+}
+""")
+
+test.run()
+
+test.run(program = test.workpath('aaa'), stdout = "aaa_main.c aaa.s\n")
+test.run(program = test.workpath('bbb'), stdout = "bbb_main.c bbb.s\n")
+test.run(program = test.workpath('ccc'), stdout = "ccc_main.c ccc.S\n")
+
+test.must_match('wrapper.out', "wrapper.py: bbb.s\n")
+
+test.write("ccc.h", """\
+#define STRING "ccc.S 2"
+""")
+
+test.run()
+test.run(program = test.workpath('ccc'), stdout = "ccc_main.c ccc.S 2\n")
+
+test.unlink('wrapper.out')
+
+
+
+test.pass_test()
diff --git a/test/AS/ml.py b/test/AS/ml.py
new file mode 100644
index 0000000..0cb0e9e
--- /dev/null
+++ b/test/AS/ml.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python
+#
+# __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__"
+
+"""
+Verify correct use of the live 'ml' assembler.
+"""
+
+import os
+import string
+import sys
+
+import TestSCons
+
+_python_ = TestSCons._python_
+_exe = TestSCons._exe
+
+test = TestSCons.TestSCons()
+
+if sys.platform == 'win32':
+ test.skip_test("Skipping ml test on non-win32 platform '%s'\n" % sys.platform)
+
+ml = test.where_is('ml')
+
+if not ml:
+ test.skip_test("ml not found; skipping test\n")
+
+test.write("wrapper.py",
+"""import os
+import string
+import sys
+open('%s', 'wb').write("wrapper.py\\n")
+os.system(string.join(sys.argv[1:], " "))
+""" % string.replace(test.workpath('wrapper.out'), '\\', '\\\\'))
+
+test.write('SConstruct', """
+import os
+ccc = Environment(tools = ['msvc', 'mslink', 'masm'],
+ ASFLAGS = '/nologo /coff')
+ccc['ENV']['PATH'] = ccc['ENV']['PATH'] + os.pathsep + os.environ['PATH']
+ddd = ccc.Clone(AS = r'%(_python_)s wrapper.py ' + ccc['AS'])
+ccc.Program(target = 'ccc', source = ['ccc.asm', 'ccc_main.c'])
+ddd.Program(target = 'ddd', source = ['ddd.asm', 'ddd_main.c'])
+""" % locals())
+
+test.write('ccc.asm',
+"""
+DSEG segment
+ PUBLIC _name
+_name byte "ccc.asm",0
+DSEG ends
+ end
+""")
+
+test.write('ddd.asm',
+"""
+DSEG segment
+ PUBLIC _name
+_name byte "ddd.asm",0
+DSEG ends
+ end
+""")
+
+test.write('ccc_main.c', r"""
+extern char name[];
+
+int
+main(int argc, char *argv[])
+{
+ argv[argc++] = "--";
+ printf("ccc_main.c %s\n", name);
+ exit (0);
+}
+""")
+
+test.write('ddd_main.c', r"""
+extern char name[];
+
+int
+main(int argc, char *argv[])
+{
+ argv[argc++] = "--";
+ printf("ddd_main.c %s\n", name);
+ exit (0);
+}
+""")
+
+test.run(arguments = 'ccc' + _exe, stderr = None)
+
+test.run(program = test.workpath('ccc'), stdout = "ccc_main.c ccc.asm\n")
+
+test.must_not_exist('wrapper.out')
+
+test.run(arguments = 'ddd' + _exe)
+
+test.run(program = test.workpath('ddd'), stdout = "ddd_main.c ddd.asm\n")
+
+test.must_match('wrapper.out', "wrapper.py\n")
+
+
+
+test.pass_test()
diff --git a/test/AS/nasm.py b/test/AS/nasm.py
new file mode 100644
index 0000000..f96db7e
--- /dev/null
+++ b/test/AS/nasm.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python
+#
+# __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__"
+
+"""
+Verify correct use of the live 'nasm' assembler.
+"""
+
+import os
+import string
+import sys
+
+import TestSCons
+
+_python_ = TestSCons._python_
+_exe = TestSCons._exe
+
+test = TestSCons.TestSCons()
+
+nasm = test.where_is('nasm')
+
+if not nasm:
+ test.skip_test('nasm not found; skipping test\n')
+
+if string.find(sys.platform, 'linux') == -1:
+ test.skip_test("skipping test on non-Linux platform '%s'\n" % sys.platfrom)
+
+try:
+ import popen2
+ stdout = popen2.popen2('nasm -v')[0]
+except OSError:
+ test.skip_test('could not determine nasm version; skipping test\n')
+else:
+ version = string.split(stdout.read())[2]
+ if version[:4] != '0.98':
+ test.skip_test("skipping test of nasm version %s\n" % version)
+
+ machine = os.uname()[4]
+ if not machine in ('i386', 'i486', 'i586', 'i686'):
+ fmt = "skipping test of nasm %s on non-x86 machine '%s'\n"
+ test.skip_test(fmt % (version, machine))
+
+# Allow flexibility about the type of object/executable format
+# needed on different systems. Format_map is a dict that maps
+# sys.platform substrings to the correct argument for the nasm -f
+# option. The default is "elf," which seems to be a reasonable
+# lowest common denominator (works on both Linux and FreeBSD,
+# anyway...).
+nasm_format = 'elf'
+format_map = {}
+for k, v in format_map.items():
+ if string.find(sys.platform, k) != -1:
+ nasm_format = v
+ break
+
+test.write("wrapper.py",
+"""import os
+import string
+import sys
+open('%s', 'wb').write("wrapper.py\\n")
+os.system(string.join(sys.argv[1:], " "))
+""" % string.replace(test.workpath('wrapper.out'), '\\', '\\\\'))
+
+test.write('SConstruct', """
+eee = Environment(tools = ['gcc', 'gnulink', 'nasm'],
+ ASFLAGS = '-f %(nasm_format)s')
+fff = eee.Clone(AS = r'%(_python_)s wrapper.py ' + WhereIs('nasm'))
+eee.Program(target = 'eee', source = ['eee.asm', 'eee_main.c'])
+fff.Program(target = 'fff', source = ['fff.asm', 'fff_main.c'])
+""" % locals())
+
+test.write('eee.asm',
+"""
+global name
+name:
+ db 'eee.asm',0
+""")
+
+test.write('fff.asm',
+"""
+global name
+name:
+ db 'fff.asm',0
+""")
+
+test.write('eee_main.c', r"""
+extern char name[];
+
+int
+main(int argc, char *argv[])
+{
+ argv[argc++] = "--";
+ printf("eee_main.c %s\n", name);
+ exit (0);
+}
+""")
+
+test.write('fff_main.c', r"""
+#include <stdio.h>
+#include <stdlib.h>
+
+extern char name[];
+
+int
+main(int argc, char *argv[])
+{
+ argv[argc++] = "--";
+ printf("fff_main.c %s\n", name);
+ exit (0);
+}
+""")
+
+test.run(arguments = 'eee' + _exe, stderr = None)
+
+test.run(program = test.workpath('eee'), stdout = "eee_main.c eee.asm\n")
+
+test.must_not_exist('wrapper.out')
+
+test.run(arguments = 'fff' + _exe)
+
+test.run(program = test.workpath('fff'), stdout = "fff_main.c fff.asm\n")
+
+test.must_match('wrapper.out', "wrapper.py\n")
+
+
+
+test.pass_test()
diff --git a/test/AddOption/basic.py b/test/AddOption/basic.py
new file mode 100644
index 0000000..57ee11e
--- /dev/null
+++ b/test/AddOption/basic.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+#
+# __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__"
+
+"""
+Verify the help text when the AddOption() function is used (and when
+it's not).
+"""
+
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+env = Environment()
+AddOption('--force',
+ action="store_true",
+ help='force installation (overwrite any existing files)')
+AddOption('--prefix',
+ nargs=1,
+ dest='prefix',
+ action='store',
+ type='string',
+ metavar='DIR',
+ help='installation prefix')
+f = GetOption('force')
+if f:
+ f = "True"
+print f
+print GetOption('prefix')
+""")
+
+test.run('-Q -q .',
+ stdout="None\nNone\n")
+
+test.run('-Q -q . --force',
+ stdout="True\nNone\n")
+
+test.run('-Q -q . --prefix=/home/foo',
+ stdout="None\n/home/foo\n")
+
+test.run('-Q -q . -- --prefix=/home/foo --force',
+ status=1,
+ stdout="None\nNone\n")
+
+test.pass_test()
diff --git a/test/AddOption/help.py b/test/AddOption/help.py
new file mode 100644
index 0000000..d7f02f1
--- /dev/null
+++ b/test/AddOption/help.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+#
+# __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__"
+
+"""
+Verify the help text when the AddOption() function is used (and when
+it's not).
+"""
+
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+env = Environment()
+AddOption('--force',
+ action="store_true",
+ help='force installation (overwrite existing files)')
+AddOption('--prefix',
+ nargs=1,
+ dest='prefix',
+ action='store',
+ type='string',
+ metavar='DIR',
+ help='installation prefix')
+""")
+
+expected_lines = [
+ 'Local Options:',
+ ' --force force installation (overwrite existing files)',
+ ' --prefix=DIR installation prefix',
+]
+
+test.run(arguments = '-h')
+lines = string.split(test.stdout(), '\n')
+missing = filter(lambda e, l=lines: not e in l, expected_lines)
+
+if missing:
+ print "====== STDOUT:"
+ print test.stdout()
+ print "====== Missing the following lines in the above AddOption() help output:"
+ print string.join(missing, "\n")
+ test.fail_test()
+
+test.unlink('SConstruct')
+
+test.run(arguments = '-h')
+lines = string.split(test.stdout(), '\n')
+unexpected = filter(lambda e, l=lines: e in l, expected_lines)
+
+if unexpected:
+ print "====== STDOUT:"
+ print test.stdout()
+ print "====== Unexpected lines in the above non-AddOption() help output:"
+ print string.join(unexpected, "\n")
+ test.fail_test()
+
+test.pass_test()
diff --git a/test/Configure/ConfigureDryRunError.py b/test/Configure/ConfigureDryRunError.py
index 04fbc75..65c6403 100644
--- a/test/Configure/ConfigureDryRunError.py
+++ b/test/Configure/ConfigureDryRunError.py
@@ -57,20 +57,24 @@ if not (r1 and not r2):
Exit(1)
""" % (lib))
-test.run(arguments='-n', status=2, stderr="""
+expect = """
scons: *** Cannot create configure directory ".sconf_temp" within a dry-run.
-File "%(SConstruct_path)s", line 5, in ?
-""" % locals())
+""" + test.python_file_line(SConstruct_path, 5)
+
+test.run(arguments='-n', status=2, stderr=expect)
test.must_not_exist('config.log')
test.subdir('.sconf_temp')
conftest_0_c = os.path.join(".sconf_temp", "conftest_0.c")
+SConstruct_file_line = test.python_file_line(SConstruct_path, 6)[:-1]
-test.run(arguments='-n', status=2, stderr="""
+expect = """
scons: *** Cannot update configure test "%(conftest_0_c)s" within a dry-run.
-File "%(SConstruct_path)s", line 6, in ?
-""" % locals())
+%(SConstruct_file_line)s
+""" % locals()
+
+test.run(arguments='-n', status=2, stderr=expect)
test.run()
test.checkLogAndStdout( ["Checking for C library %s... " % lib,
diff --git a/test/Configure/option--config.py b/test/Configure/option--config.py
index 95a0d3b..807ddb5 100644
--- a/test/Configure/option--config.py
+++ b/test/Configure/option--config.py
@@ -60,11 +60,14 @@ test.write(['include', 'non_system_header1.h'], """
""")
conftest_0_c = os.path.join(".sconf_temp", "conftest_0.c")
+SConstruct_file_line = test.python_file_line(SConstruct_path, 6)[:-1]
-test.run(arguments='--config=cache', status=2, stderr="""
+expect = """
scons: *** "%(conftest_0_c)s" is not yet built and cache is forced.
-File "%(SConstruct_path)s", line 6, in ?
-""" % locals())
+%(SConstruct_file_line)s
+""" % locals()
+
+test.run(arguments='--config=cache', status=2, stderr=expect)
test.run(arguments='--config=auto')
test.checkLogAndStdout( ["Checking for C header file non_system_header1.h... ",
diff --git a/test/Java/RMIC.py b/test/Java/RMIC.py
index a66f885..2ab1804 100644
--- a/test/Java/RMIC.py
+++ b/test/Java/RMIC.py
@@ -102,7 +102,7 @@ if not where_javac:
test.skip_test("Could not find Java javac, skipping non-simulated test(s).\n")
if test.detect_tool('rmic', ENV=ENV):
- where_rmic = test.detect('JAVAC', 'rmic', ENV=ENV)
+ where_rmic = test.detect('RMIC', 'rmic', ENV=ENV)
else:
where_rmic = test.where_is('rmic')
if not where_rmic:
diff --git a/test/LIBPATH.py b/test/LIBPATH.py
index 1ea7ed8..7fd4f9b 100644
--- a/test/LIBPATH.py
+++ b/test/LIBPATH.py
@@ -45,7 +45,7 @@ env1 = Environment(LIBS = [ 'foo1' ],
LIBPATH = [ '$FOO' ],
FOO='./lib1')
-f1 = env1.Object('f1', 'f1.c')
+f1 = env1.SharedObject('f1', 'f1.c')
env1.Program(target = 'prog', source = 'prog.c')
env1.Library(target = './lib1/foo1', source = f1)
@@ -127,7 +127,7 @@ test.write('SConstruct', """
env1 = Environment(LIBS = [ 'foo1' ],
LIBPATH = [ './lib1', './lib2' ])
-f1 = env1.Object('f1', 'f1.c')
+f1 = env1.SharedObject('f1', 'f1.c')
env1.Program(target = 'prog', source = 'prog.c')
env1.Library(target = './lib1/foo1', source = f1)
diff --git a/test/NodeOps.py b/test/NodeOps.py
index 17a0f02..3869228 100644
--- a/test/NodeOps.py
+++ b/test/NodeOps.py
@@ -149,10 +149,10 @@ def exists_test(node):
node.is_derived()
node.is_pseudo_derived()
import SCons.Script
- if SCons.Script.Main.options.noexec:
+ if GetOption('no_exec'):
if (before,via_node,after) != (False,False,False):
import sys
- sys.stderr.write('BuildDir exits() populated during dryrun!\n')
+ sys.stderr.write('BuildDir exists() populated during dryrun!\n')
sys.exit(-2)
else:
if (before,via_node,after) != (False,True,True):
diff --git a/test/Options/BoolOption.py b/test/Options/BoolOption.py
index aab2b20..38b30d5 100644
--- a/test/Options/BoolOption.py
+++ b/test/Options/BoolOption.py
@@ -78,8 +78,7 @@ check([str(False), str(True)])
expect_stderr = """
scons: *** Error converting option: warnings
Invalid value for boolean option: irgendwas
-File "%(SConstruct_path)s", line 9, in ?
-""" % locals()
+""" + test.python_file_line(SConstruct_path, 9)
test.run(arguments='warnings=irgendwas', stderr = expect_stderr, status=2)
diff --git a/test/Options/EnumOption.py b/test/Options/EnumOption.py
index 5e29477..610a485 100644
--- a/test/Options/EnumOption.py
+++ b/test/Options/EnumOption.py
@@ -82,22 +82,19 @@ check(['full', 'KdE', 'eins'])
expect_stderr = """
scons: *** Invalid value for option debug: FULL
-File "%(SConstruct_path)s", line 18, in ?
-""" % locals()
+""" + test.python_file_line(SConstruct_path, 18)
test.run(arguments='debug=FULL', stderr=expect_stderr, status=2)
expect_stderr = """
scons: *** Invalid value for option guilib: irgendwas
-File "%(SConstruct_path)s", line 18, in ?
-""" % locals()
+""" + test.python_file_line(SConstruct_path, 18)
test.run(arguments='guilib=IrGeNdwas', stderr=expect_stderr, status=2)
expect_stderr = """
scons: *** Invalid value for option some: irgendwas
-File "%(SConstruct_path)s", line 18, in ?
-""" % locals()
+""" + test.python_file_line(SConstruct_path, 18)
test.run(arguments='some=IrGeNdwas', stderr=expect_stderr, status=2)
diff --git a/test/Options/ListOption.py b/test/Options/ListOption.py
index fddfc5f..b6ba306 100644
--- a/test/Options/ListOption.py
+++ b/test/Options/ListOption.py
@@ -96,8 +96,7 @@ check(['gl,qt', '0', 'gl qt', 'gl qt'])
expect_stderr = """
scons: *** Error converting option: shared
Invalid value(s) for option: foo
-File "%(SConstruct_path)s", line 14, in ?
-""" % locals()
+""" + test.python_file_line(SConstruct_path, 14)
test.run(arguments='shared=foo', stderr=expect_stderr, status=2)
@@ -106,32 +105,28 @@ test.run(arguments='shared=foo', stderr=expect_stderr, status=2)
expect_stderr = """
scons: *** Error converting option: shared
Invalid value(s) for option: foo
-File "%(SConstruct_path)s", line 14, in ?
-""" % locals()
+""" + test.python_file_line(SConstruct_path, 14)
test.run(arguments='shared=foo,ical', stderr=expect_stderr, status=2)
expect_stderr = """
scons: *** Error converting option: shared
Invalid value(s) for option: foo
-File "%(SConstruct_path)s", line 14, in ?
-""" % locals()
+""" + test.python_file_line(SConstruct_path, 14)
test.run(arguments='shared=ical,foo', stderr=expect_stderr, status=2)
expect_stderr = """
scons: *** Error converting option: shared
Invalid value(s) for option: foo
-File "%(SConstruct_path)s", line 14, in ?
-""" % locals()
+""" + test.python_file_line(SConstruct_path, 14)
test.run(arguments='shared=ical,foo,x11', stderr=expect_stderr, status=2)
expect_stderr = """
scons: *** Error converting option: shared
Invalid value(s) for option: foo,bar
-File "%(SConstruct_path)s", line 14, in ?
-""" % locals()
+""" + test.python_file_line(SConstruct_path, 14)
test.run(arguments='shared=foo,x11,,,bar', stderr=expect_stderr, status=2)
diff --git a/test/Options/PackageOption.py b/test/Options/PackageOption.py
index d285338..79d4dda 100644
--- a/test/Options/PackageOption.py
+++ b/test/Options/PackageOption.py
@@ -80,8 +80,7 @@ check([test.workpath()])
expect_stderr = """
scons: *** Path does not exist for option x11: /non/existing/path/
-File "%(SConstruct_path)s", line 10, in ?
-""" % locals()
+""" + test.python_file_line(SConstruct_path, 10)
test.run(arguments='x11=/non/existing/path/', stderr=expect_stderr, status=2)
diff --git a/test/Options/PathOption.py b/test/Options/PathOption.py
index d22071e..23fec6e 100644
--- a/test/Options/PathOption.py
+++ b/test/Options/PathOption.py
@@ -90,17 +90,18 @@ test.run(arguments=['qtdir=%s' % qtpath, 'qt_libraries=%s' % libpath])
check([qtpath, libpath, libpath])
qtpath = os.path.join(workpath, 'non', 'existing', 'path')
+SConstruct_file_line = test.python_file_line(test.workpath('SConstruct'), 11)[:-1]
expect_stderr = """
scons: *** Path for option qtdir does not exist: %(qtpath)s
-File "%(SConstruct_path)s", line 11, in ?
+%(SConstruct_file_line)s
""" % locals()
test.run(arguments=['qtdir=%s' % qtpath], stderr=expect_stderr, status=2)
expect_stderr = """
scons: *** Path for option qt_libraries does not exist: %(qtpath)s
-File "%(SConstruct_path)s", line 11, in ?
+%(SConstruct_file_line)s
""" % locals()
test.run(arguments=['qt_libraries=%s' % qtpath], stderr=expect_stderr, status=2)
@@ -167,9 +168,11 @@ print env['X']
Default(env.Alias('dummy', None))
""" % default_file)
+SConstruct_file_line = test.python_file_line(test.workpath('SConstruct'), 6)[:-1]
+
expect_stderr = """
scons: *** File path for option X does not exist: %(default_file)s
-File "%(SConstruct_path)s", line 6, in ?
+%(SConstruct_file_line)s
""" % locals()
test.run(status=2, stderr=expect_stderr)
@@ -181,7 +184,7 @@ check([default_file])
expect_stderr = """
scons: *** File path for option X is a directory: %(existing_subdir)s
-File "%(SConstruct_path)s", line 6, in ?
+%(SConstruct_file_line)s
""" % locals()
test.run(arguments=['X=%s' % existing_subdir], status=2, stderr=expect_stderr)
@@ -191,7 +194,7 @@ check([existing_file])
expect_stderr = """
scons: *** File path for option X does not exist: %(non_existing_file)s
-File "%(SConstruct_path)s", line 6, in ?
+%(SConstruct_file_line)s
""" % locals()
test.run(arguments=['X=%s' % non_existing_file], status=2, stderr=expect_stderr)
@@ -213,7 +216,7 @@ Default(env.Alias('dummy', None))
expect_stderr = """
scons: *** Directory path for option X does not exist: %(default_subdir)s
-File "%(SConstruct_path)s", line 6, in ?
+%(SConstruct_file_line)s
""" % locals()
test.run(status=2, stderr=expect_stderr)
@@ -225,7 +228,7 @@ check([default_subdir])
expect_stderr = """
scons: *** Directory path for option X is a file: %(existing_file)s
-File "%(SConstruct_path)s", line 6, in ?
+%(SConstruct_file_line)s
""" % locals()
test.run(arguments=['X=%s' % existing_file],
@@ -237,7 +240,7 @@ check([existing_subdir])
expect_stderr = """
scons: *** Directory path for option X does not exist: %(non_existing_subdir)s
-File "%(SConstruct_path)s", line 6, in ?
+%(SConstruct_file_line)s
""" % locals()
test.run(arguments=['X=%s' % non_existing_subdir],
@@ -264,7 +267,7 @@ check([default_subdir])
expect_stderr = """
scons: *** Path for option X is a file, not a directory: %(existing_file)s
-File "%(SConstruct_path)s", line 6, in ?
+%(SConstruct_file_line)s
""" % locals()
test.run(arguments=['X=%s' % existing_file], status=2, stderr=expect_stderr)
diff --git a/test/Perforce/Perforce.py b/test/Perforce/Perforce.py
index a24c65d..b409210 100644
--- a/test/Perforce/Perforce.py
+++ b/test/Perforce/Perforce.py
@@ -40,6 +40,8 @@ class TestPerforce(TestSCons.TestSCons):
def __init__(self, *args, **kw):
apply(TestSCons.TestSCons.__init__, (self,)+args, kw)
+ self.p4d = None
+
self.p4path = self.where_is('p4')
if not self.p4path:
self.skip_test("Could not find 'p4'; skipping test(s).\n")
diff --git a/test/QT/Tool.py b/test/QT/Tool.py
index 9079225..3ccabf7 100644
--- a/test/QT/Tool.py
+++ b/test/QT/Tool.py
@@ -30,14 +30,20 @@ works properly. This was broken in 0.96.95.
The configuration here is a moderately stripped-down version of the
real-world configuration for lprof (lprof.sourceforge.net). It's probably
-not completely minimal, but we're leaving it as since it represents a
+not completely minimal, but we're leaving it as-is since it represents a
good real-world sanity check on the interaction of some key subsystems.
"""
+import os
+
import TestSCons
test = TestSCons.TestSCons()
+if not os.environ.get('QTDIR', None):
+ x ="External environment variable $QTDIR not set; skipping test(s).\n"
+ test.skip_test(x)
+
test.write('SConstruct', """
import os
import os.path
diff --git a/test/QT/warnings.py b/test/QT/warnings.py
index 0fd3f66..baa1e6f 100644
--- a/test/QT/warnings.py
+++ b/test/QT/warnings.py
@@ -85,13 +85,13 @@ if moc:
expect = """
scons: warning: Could not detect qt, using moc executable as a hint \(QTDIR=%s\)
-File "%s", line \d+, in \?
+File "%s", line \d+, in (\?|<module>)
""" % (qtdir, re.escape(SConstruct_path))
else:
- expect = """
+ expect = r"""
scons: warning: Could not detect qt, using empty QTDIR
-File "%s", line \d+, in \?
+File "%s", line \d+, in (\?|<module>)
""" % re.escape(SConstruct_path)
test.fail_test(not test.match_re(test.stderr(), expect))
diff --git a/test/RCS/diskcheck.py b/test/RCS/diskcheck.py
index 78120f6..07cb170 100644
--- a/test/RCS/diskcheck.py
+++ b/test/RCS/diskcheck.py
@@ -115,13 +115,15 @@ test.write('bbb.in', "checked-out bbb.in\n")
test.write(['sub', 'eee.in'], "checked-out sub/eee.in\n")
+sub_SConscript = os.path.join('sub', 'SConscript')
+SConstruct_file_line = test.python_file_line(test.workpath('SConstruct'), 23)[:-1]
expect = """\
-scons: warning: Ignoring missing SConscript '%s'
-File "%s", line 23, in ?
+scons: warning: Ignoring missing SConscript '%(sub_SConscript)s'
+%(SConstruct_file_line)s
scons: *** Source `aaa.in' not found, needed by target `aaa.out'. Stop.
-""" % (os.path.join('sub', 'SConscript'), test.workpath('SConstruct'))
+""" % locals()
test.run(status=2, stderr=expect)
diff --git a/test/Repository/RMIC.py b/test/Repository/RMIC.py
index e4a4459..ebe6a83 100644
--- a/test/Repository/RMIC.py
+++ b/test/Repository/RMIC.py
@@ -47,7 +47,7 @@ if not where_javac:
test.skip_test("Could not find Java javac, skipping test(s).\n")
if test.detect_tool('rmic', ENV=ENV):
- where_rmic = test.detect('JAVAC', 'rmic', ENV=ENV)
+ where_rmic = test.detect('RMIC', 'rmic', ENV=ENV)
else:
where_rmic = test.where_is('rmic')
if not where_rmic:
diff --git a/test/SCCS/diskcheck.py b/test/SCCS/diskcheck.py
index 66566fa..a9df89b 100644
--- a/test/SCCS/diskcheck.py
+++ b/test/SCCS/diskcheck.py
@@ -93,17 +93,18 @@ test.write(['bbb.in'], "checked-out bbb.in\n")
test.write(['sub', 'eee.in'], "checked-out sub/eee.in\n")
+sub_SConscript = os.path.join('sub', 'SConscript')
+SConstruct_file_line = test.python_file_line(test.workpath('SConstruct'), 17)[:-1]
+
expect = """\
-scons: warning: Ignoring missing SConscript '%s'
-File "%s", line 17, in ?
+scons: warning: Ignoring missing SConscript '%(sub_SConscript)s'
+%(SConstruct_file_line)s
scons: *** Source `aaa.in' not found, needed by target `aaa.out'. Stop.
-""" % (os.path.join('sub', 'SConscript'), test.workpath('SConstruct'))
+""" % locals()
test.run(status=2, stderr=expect)
-test.run(arguments = '--diskcheck=none', status=2, stderr=expect)
-
test.run(arguments = '--diskcheck=sccs', stderr = None)
lines = string.split("""
diff --git a/test/SWIG/live.py b/test/SWIG/live.py
index c6b45d2..93336ca 100644
--- a/test/SWIG/live.py
+++ b/test/SWIG/live.py
@@ -28,6 +28,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
Test SWIG behavior with a live, installed SWIG.
"""
+import os.path
import string
import sys
@@ -73,6 +74,14 @@ if sys.platform == 'darwin':
# (see top of file for further explanation)
frameworks = '-framework Python'
platform_sys_prefix = '/System/Library/Frameworks/Python.framework/Versions/%s/' % version
+
+python_include_dir = os.path.join(platform_sys_prefix,
+ 'include',
+ 'python' + version)
+Python_h = os.path.join(python_include_dir, 'Python.h')
+
+if not os.path.exists(Python_h):
+ test.skip_test('Can not find %s, skipping test.\n' % Python_h)
test.write("wrapper.py",
"""import os
@@ -84,7 +93,7 @@ os.system(string.join(sys.argv[1:], " "))
test.write('SConstruct', """
foo = Environment(SWIGFLAGS='-python',
- CPPPATH='%(platform_sys_prefix)s/include/python%(version)s/',
+ CPPPATH='%(python_include_dir)s/',
LDMODULEPREFIX='%(ldmodule_prefix)s',
LDMODULESUFFIX='%(_dll)s',
FRAMEWORKSFLAGS='%(frameworks)s',
@@ -108,6 +117,12 @@ test.write("foo.i", """\
%module foo
%{
/* Put header files here (optional) */
+/*
+ * This duplication shouldn't be necessary, I guess, but it seems
+ * to suppress "cast to pointer from integer of different size"
+ * warning messages on some systems.
+ */
+extern char *foo_string();
%}
extern char *foo_string();
@@ -125,6 +140,12 @@ test.write("bar.i", """\
%module \t bar
%{
/* Put header files here (optional) */
+/*
+ * This duplication shouldn't be necessary, I guess, but it seems
+ * to suppress "cast to pointer from integer of different size"
+ * warning messages on some systems.
+ */
+extern char *bar_string();
%}
extern char *bar_string();
diff --git a/test/SWIG/remove-modules.py b/test/SWIG/remove-modules.py
index cac9677..1a48c9e 100644
--- a/test/SWIG/remove-modules.py
+++ b/test/SWIG/remove-modules.py
@@ -29,6 +29,7 @@ Verify that swig-generated modules are removed.
The %module directive specifies the module name.
"""
+import os.path
import sys
import TestSCons
@@ -62,6 +63,14 @@ if sys.platform == 'darwin':
# (see top of file for further explanation)
frameworks = '-framework Python'
platform_sys_prefix = '/System/Library/Frameworks/Python.framework/Versions/%s/' % version
+
+python_include_dir = os.path.join(platform_sys_prefix,
+ 'include',
+ 'python' + version)
+Python_h = os.path.join(python_include_dir, 'Python.h')
+
+if not os.path.exists(Python_h):
+ test.skip_test('Can not find %s, skipping test.\n' % Python_h)
test.write("module.i", """\
diff --git a/test/Scanner/exception.py b/test/Scanner/exception.py
index 7615888..d8204db 100644
--- a/test/Scanner/exception.py
+++ b/test/Scanner/exception.py
@@ -49,7 +49,7 @@ def kfile_scan(node, env, target, arg):
contents = node.get_contents()
exceptions = exception_re.findall(contents)
if exceptions:
- raise "kfile_scan error: %s" % exceptions[0]
+ raise Exception, "kfile_scan error: %s" % exceptions[0]
includes = include_re.findall(contents)
return includes
diff --git a/test/Script-import.py b/test/Script-import.py
index 544960b..3df980f 100644
--- a/test/Script-import.py
+++ b/test/Script-import.py
@@ -60,24 +60,33 @@ import SCons.Script
SCons.Script.BuildTask
SCons.Script.CleanTask
SCons.Script.QuestionTask
-#SCons.Script.PrintHelp
-SCons.Script.OptParser
-SCons.Script.SConscriptSettableOptions
-
-SCons.Script.keep_going_on_error
-#SCons.Script.print_dtree
-SCons.Script.print_explanations
-SCons.Script.print_includes
-SCons.Script.print_objects
-SCons.Script.print_time
-#SCons.Script.print_tree
-SCons.Script.memory_stats
-SCons.Script.ignore_errors
-#SCons.Script.sconscript_time
-#SCons.Script.command_time
-#SCons.Script.exit_status
-#SCons.Script.profiling
-SCons.Script.repositories
+
+old_SCons_Script_variables = [
+ 'PrintHelp',
+ 'OptParser',
+ 'keep_going_on_error',
+ 'print_explanations',
+ 'print_includes',
+ 'print_objects',
+ 'print_time',
+ 'memory_stats',
+ 'ignore_errors',
+ 'repositories',
+ 'print_dtree',
+ 'print_tree',
+ 'sconscript_time',
+ 'command_time',
+ 'exit_status',
+ 'profiling',
+]
+
+for var in old_SCons_Script_variables:
+ try:
+ getattr(SCons.Script, var)
+ except AttributeError:
+ pass
+ else:
+ raise Exception, "unexpected variable SCons.Script.%s" % var
""")
test.write("m4.py", """\
diff --git a/test/TEX/auxiliaries.py b/test/TEX/auxiliaries.py
index e34ddaa..39984b3 100644
--- a/test/TEX/auxiliaries.py
+++ b/test/TEX/auxiliaries.py
@@ -123,9 +123,8 @@ pdf_output_1 = test.read(['build', 'docs', 'test.pdf'])
ps_output_1 = test.read(['build', 'docs', 'test.ps'])
# Adding blank lines will cause SCons to re-run the builds, but the
-# actual contents of the output files shouldn't be any different.
-# This assumption won't work if it's ever used with a toolchain that does
-# something to the output like put a commented-out timestamp in a header.
+# actual contents of the output files should be the same modulo
+# the CreationDate header.
test.write(['docs', 'test.tex'], tex_input + "\n\n\n")
test.run(stderr=None)
@@ -133,7 +132,14 @@ test.run(stderr=None)
pdf_output_2 = test.read(['build', 'docs', 'test.pdf'])
ps_output_2 = test.read(['build', 'docs', 'test.ps'])
-test.fail_test(pdf_output_1 != pdf_output_2)
-test.fail_test(ps_output_1 != ps_output_2)
+
+
+pdf_output_1 = test.normalize_pdf(pdf_output_1)
+pdf_output_2 = test.normalize_pdf(pdf_output_2)
+
+assert pdf_output_1 == pdf_output_2, test.diff_substr(pdf_output_1, pdf_output_2, 80, 80)
+assert ps_output_1 == ps_output_2, test.diff_substr(ps_output_1, ps_output_2, 80, 80)
+
+
test.pass_test()
diff --git a/test/TEX/bibtex-latex-rerun.py b/test/TEX/bibtex-latex-rerun.py
index 353d9a4..a0ba5c2 100644
--- a/test/TEX/bibtex-latex-rerun.py
+++ b/test/TEX/bibtex-latex-rerun.py
@@ -95,10 +95,16 @@ test.run()
pdf_output_3 = test.read('bibtest.pdf')
-# If the PDF file is now different than the second run, then something
-# else odd has happened, so fail. (Note that this test will be incorrect
-# if the tool does something like insert a timestamp in the PDF file.)
-test.fail_test(pdf_output_2 != pdf_output_3)
+
+
+# If the PDF file is now different than the second run, modulo the
+# creation timestamp and the ID, then something else odd has happened,
+# so fail.
+
+pdf_output_2 = test.normalize_pdf(pdf_output_2)
+pdf_output_3 = test.normalize_pdf(pdf_output_3)
+
+assert pdf_output_2 == pdf_output_3, test.diff_substr(pdf_output_2, pdf_output_3, 80, 80)
diff --git a/test/bad-variables.py b/test/bad-variables.py
index cb6b357..0618c5e 100644
--- a/test/bad-variables.py
+++ b/test/bad-variables.py
@@ -43,8 +43,7 @@ env['foo-bar'] = 1
expect_stderr = """
scons: *** Illegal construction variable `foo-bar'
-File "%(SConstruct_path)s", line 2, in ?
-""" % locals()
+""" + test.python_file_line(SConstruct_path, 2)
test.run(arguments='.', status=2, stderr=expect_stderr)
@@ -59,10 +58,10 @@ env = Environment()
env['foo(bar)'] = 1
""")
+
expect_stderr = """
scons: *** Illegal construction variable `foo(bar)'
-File "%(SConscript_path)s", line 2, in ?
-""" % locals()
+""" + test.python_file_line(SConscript_path, 2)
test.run(arguments='.', status=2, stderr=expect_stderr)
diff --git a/test/compat/all.py b/test/compat/all.py
new file mode 100644
index 0000000..ee8f9ef
--- /dev/null
+++ b/test/compat/all.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+#
+# __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__"
+
+"""
+Verify that we can use the any() function (in any supported Python
+version we happen to be testing).
+
+This test can be retired at some point in the distant future when Python
+2.5 becomes the minimum version supported by SCons.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+print all([True, 1]) and "YES" or "NO"
+print all([0]) and "YES" or "NO"
+SConscript('SConscript')
+""")
+
+test.write('SConscript', """\
+print all([1, False]) and "YES" or "NO"
+print all([True, None]) and "YES" or "NO"
+""")
+
+expect = """\
+YES
+NO
+NO
+NO
+"""
+
+test.run(arguments = '-Q -q', stdout = expect)
+
+test.pass_test()
diff --git a/test/compat/any.py b/test/compat/any.py
new file mode 100644
index 0000000..cab191c
--- /dev/null
+++ b/test/compat/any.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+#
+# __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__"
+
+"""
+Verify that we can use the any() function (in any supported Python
+version we happen to be testing).
+
+This test can be retired at some point in the distant future when Python
+2.5 becomes the minimum version supported by SCons.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+print any([True, False]) and "YES" or "NO"
+print any([1]) and "YES" or "NO"
+SConscript('SConscript')
+""")
+
+test.write('SConscript', """\
+print any([0, False]) and "YES" or "NO"
+""")
+
+expect = """\
+YES
+YES
+NO
+"""
+
+test.run(arguments = '-Q -q', stdout = expect)
+
+test.pass_test()
diff --git a/test/exceptions.py b/test/exceptions.py
index 01a2855..34c24f4 100644
--- a/test/exceptions.py
+++ b/test/exceptions.py
@@ -39,7 +39,7 @@ SConstruct_path = test.workpath('SConstruct')
test.write(SConstruct_path, """\
def func(source = None, target = None, env = None):
- raise "func exception"
+ raise Exception, "func exception"
B = Builder(action = func)
env = Environment(BUILDERS = { 'B' : B })
env.B(target = 'foo.out', source = 'foo.in')
@@ -55,8 +55,8 @@ Traceback \((most recent call|innermost) last\):
)*( File ".+", line \d+, in \S+
[^\n]+
)* File "%s", line 2, in func
- raise "func exception"
-func exception
+ raise Exception, "func exception"
+Exception: func exception
""" % re.escape(SConstruct_path)
test.run(arguments = "foo.out", stderr = expected_stderr, status = 2)
diff --git a/test/explain.py b/test/explain.py
deleted file mode 100644
index 9b721d2..0000000
--- a/test/explain.py
+++ /dev/null
@@ -1,561 +0,0 @@
-#!/usr/bin/env python
-#
-# __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__"
-
-"""
-Test the --debug=explain option.
-"""
-
-import os.path
-import string
-import sys
-import TestSCons
-
-_python_ = TestSCons._python_
-
-test = TestSCons.TestSCons()
-
-test.subdir('work1', ['work1', 'src'], ['work1', 'src', 'subdir'],
- 'work4', ['work4', 'src'], ['work4', 'src', 'subdir'],
- 'work5')
-
-subdir_file7 = os.path.join('subdir', 'file7')
-subdir_file7_in = os.path.join('subdir', 'file7.in')
-cat_py = test.workpath('cat.py')
-
-test.write(cat_py, r"""
-import sys
-
-def process(outfp, infp):
- for line in infp.readlines():
- if line[:8] == 'include ':
- file = line[8:-1]
- try:
- fp = open(file, 'rb')
- except IOError:
- import os
- print "os.getcwd() =", os.getcwd()
- raise
- process(outfp, fp)
- else:
- outfp.write(line)
-
-outfp = open(sys.argv[1], 'wb')
-for f in sys.argv[2:]:
- if f != '-':
- process(outfp, open(f, 'rb'))
-
-sys.exit(0)
-""")
-
-SConstruct_contents = """\
-import re
-
-include_re = re.compile(r'^include\s+(\S+)$', re.M)
-
-def kfile_scan(node, env, target, arg):
- contents = node.get_contents()
- includes = include_re.findall(contents)
- return includes
-
-kscan = Scanner(name = 'kfile',
- function = kfile_scan,
- argument = None,
- skeys = ['.k'])
-
-cat = Builder(action = r'%(_python_)s %(cat_py)s $TARGET $SOURCES')
-
-env = Environment()
-env.Append(BUILDERS = {'Cat':cat},
- SCANNERS = kscan)
-
-Export("env")
-SConscript('SConscript')
-env.Install('../inc', 'aaa')
-env.InstallAs('../inc/bbb.k', 'bbb.k')
-env.Install('../inc', 'ddd')
-env.InstallAs('../inc/eee', 'eee.in')
-""" % locals()
-
-args = '--debug=explain .'
-
-#############################################################################
-test.write(['work1', 'src', 'SConstruct'], SConstruct_contents)
-
-test.write(['work1', 'src', 'SConscript'], """\
-Import("env")
-env.Cat('file1', 'file1.in')
-env.Cat('file2', 'file2.k')
-env.Cat('file3', ['xxx', 'yyy', 'zzz'])
-env.Command('file4', 'file4.in',
- r'%(_python_)s %(cat_py)s $TARGET $FILE4FLAG $SOURCES',
- FILE4FLAG='-')
-env.Cat('file5', 'file5.k')
-file6 = env.Cat('file6', 'file6.in')
-AlwaysBuild(file6)
-env.Cat('subdir/file7', 'subdir/file7.in')
-""" % locals())
-
-test.write(['work1', 'src', 'aaa'], "aaa 1\n")
-test.write(['work1', 'src', 'bbb.k'], """\
-bbb.k 1
-include ccc
-include ../inc/ddd
-include ../inc/eee
-""")
-test.write(['work1', 'src', 'ccc'], "ccc 1\n")
-test.write(['work1', 'src', 'ddd'], "ddd 1\n")
-test.write(['work1', 'src', 'eee.in'], "eee.in 1\n")
-
-test.write(['work1', 'src', 'file1.in'], "file1.in 1\n")
-
-test.write(['work1', 'src', 'file2.k'], """\
-file2.k 1 line 1
-include xxx
-include yyy
-file2.k 1 line 4
-""")
-
-test.write(['work1', 'src', 'file4.in'], "file4.in 1\n")
-
-test.write(['work1', 'src', 'xxx'], "xxx 1\n")
-test.write(['work1', 'src', 'yyy'], "yyy 1\n")
-test.write(['work1', 'src', 'zzz'], "zzz 1\n")
-
-test.write(['work1', 'src', 'file5.k'], """\
-file5.k 1 line 1
-include ../inc/aaa
-include ../inc/bbb.k
-file5.k 1 line 4
-""")
-
-test.write(['work1', 'src', 'file6.in'], "file6.in 1\n")
-
-test.write(['work1', 'src', 'subdir', 'file7.in'], "subdir/file7.in 1\n")
-
-work1_inc_aaa = test.workpath('work1', 'inc', 'aaa')
-work1_inc_ddd = test.workpath('work1', 'inc', 'ddd')
-work1_inc_eee = test.workpath('work1', 'inc', 'eee')
-work1_inc_bbb_k = test.workpath('work1', 'inc', 'bbb.k')
-
-#
-expect = test.wrap_stdout("""\
-scons: building `file1' because it doesn't exist
-%(_python_)s %(cat_py)s file1 file1.in
-scons: building `file2' because it doesn't exist
-%(_python_)s %(cat_py)s file2 file2.k
-scons: building `file3' because it doesn't exist
-%(_python_)s %(cat_py)s file3 xxx yyy zzz
-scons: building `file4' because it doesn't exist
-%(_python_)s %(cat_py)s file4 - file4.in
-scons: building `%(work1_inc_aaa)s' because it doesn't exist
-Install file: "aaa" as "%(work1_inc_aaa)s"
-scons: building `%(work1_inc_ddd)s' because it doesn't exist
-Install file: "ddd" as "%(work1_inc_ddd)s"
-scons: building `%(work1_inc_eee)s' because it doesn't exist
-Install file: "eee.in" as "%(work1_inc_eee)s"
-scons: building `%(work1_inc_bbb_k)s' because it doesn't exist
-Install file: "bbb.k" as "%(work1_inc_bbb_k)s"
-scons: building `file5' because it doesn't exist
-%(_python_)s %(cat_py)s file5 file5.k
-scons: building `file6' because it doesn't exist
-%(_python_)s %(cat_py)s file6 file6.in
-scons: building `%(subdir_file7)s' because it doesn't exist
-%(_python_)s %(cat_py)s %(subdir_file7)s %(subdir_file7_in)s
-""" % locals())
-
-test.run(chdir='work1/src', arguments=args, stdout=expect)
-
-test.must_match(['work1', 'src', 'file1'], "file1.in 1\n")
-test.must_match(['work1', 'src', 'file2'], """\
-file2.k 1 line 1
-xxx 1
-yyy 1
-file2.k 1 line 4
-""")
-test.must_match(['work1', 'src', 'file3'], "xxx 1\nyyy 1\nzzz 1\n")
-test.must_match(['work1', 'src', 'file4'], "file4.in 1\n")
-test.must_match(['work1', 'src', 'file5'], """\
-file5.k 1 line 1
-aaa 1
-bbb.k 1
-ccc 1
-ddd 1
-eee.in 1
-file5.k 1 line 4
-""")
-test.must_match(['work1', 'src', 'file6'], "file6.in 1\n")
-
-#
-test.write(['work1', 'src', 'file1.in'], "file1.in 2\n")
-test.write(['work1', 'src', 'yyy'], "yyy 2\n")
-test.write(['work1', 'src', 'zzz'], "zzz 2\n")
-test.write(['work1', 'src', 'bbb.k'], "bbb.k 2\ninclude ccc\n")
-
-expect = test.wrap_stdout("""\
-scons: rebuilding `file1' because `file1.in' changed
-%(_python_)s %(cat_py)s file1 file1.in
-scons: rebuilding `file2' because `yyy' changed
-%(_python_)s %(cat_py)s file2 file2.k
-scons: rebuilding `file3' because:
- `yyy' changed
- `zzz' changed
-%(_python_)s %(cat_py)s file3 xxx yyy zzz
-scons: rebuilding `%(work1_inc_bbb_k)s' because:
- `%(work1_inc_ddd)s' is no longer a dependency
- `%(work1_inc_eee)s' is no longer a dependency
- `bbb.k' changed
-Install file: "bbb.k" as "%(work1_inc_bbb_k)s"
-scons: rebuilding `file5' because `%(work1_inc_bbb_k)s' changed
-%(_python_)s %(cat_py)s file5 file5.k
-scons: rebuilding `file6' because AlwaysBuild() is specified
-%(_python_)s %(cat_py)s file6 file6.in
-""" % locals())
-
-test.run(chdir='work1/src', arguments=args, stdout=expect)
-
-test.must_match(['work1', 'src', 'file1'], "file1.in 2\n")
-test.must_match(['work1', 'src', 'file2'], """\
-file2.k 1 line 1
-xxx 1
-yyy 2
-file2.k 1 line 4
-""")
-test.must_match(['work1', 'src', 'file3'], "xxx 1\nyyy 2\nzzz 2\n")
-test.must_match(['work1', 'src', 'file5'], """\
-file5.k 1 line 1
-aaa 1
-bbb.k 2
-ccc 1
-file5.k 1 line 4
-""")
-
-#
-test.write(['work1', 'src', 'SConscript'], """\
-Import("env")
-env.Cat('file3', ['xxx', 'yyy'])
-""")
-
-expect = test.wrap_stdout("""\
-scons: rebuilding `file3' because `zzz' is no longer a dependency
-%(_python_)s %(cat_py)s file3 xxx yyy
-""" % locals())
-
-test.run(chdir='work1/src', arguments=args, stdout=expect)
-
-test.must_match(['work1', 'src', 'file3'], "xxx 1\nyyy 2\n")
-
-#
-test.write(['work1', 'src', 'SConscript'], """\
-Import("env")
-env.Cat('file3', ['xxx', 'yyy', 'zzz'])
-""")
-
-expect = test.wrap_stdout("""\
-scons: rebuilding `file3' because `zzz' is a new dependency
-%(_python_)s %(cat_py)s file3 xxx yyy zzz
-""" % locals())
-
-test.run(chdir='work1/src', arguments=args, stdout=expect)
-
-test.must_match(['work1', 'src', 'file3'], "xxx 1\nyyy 2\nzzz 2\n")
-
-#
-test.write(['work1', 'src', 'SConscript'], """\
-Import("env")
-env.Cat('file3', ['zzz', 'yyy', 'xxx'])
-""")
-
-expect = test.wrap_stdout("""\
-scons: rebuilding `file3' because the dependency order changed:
- old: ['xxx', 'yyy', 'zzz']
- new: ['zzz', 'yyy', 'xxx']
-%(_python_)s %(cat_py)s file3 zzz yyy xxx
-""" % locals())
-
-test.run(chdir='work1/src', arguments=args, stdout=expect)
-
-test.must_match(['work1', 'src', 'file3'], "zzz 2\nyyy 2\nxxx 1\n")
-
-#
-test.write(['work1', 'src', 'SConscript'], """\
-Import("env")
-f3 = File('file3')
-env.Cat(f3, ['zzz', 'yyy', 'xxx'])
-env.AddPostAction(f3, r'%(_python_)s %(cat_py)s ${TARGET}.yyy $SOURCES yyy')
-env.AddPreAction(f3, r'%(_python_)s %(cat_py)s ${TARGET}.alt $SOURCES')
-""" % locals())
-
-expect = test.wrap_stdout("""\
-scons: rebuilding `file3' because the build action changed:
- old: %(_python_)s %(cat_py)s $TARGET $SOURCES
- new: %(_python_)s %(cat_py)s ${TARGET}.alt $SOURCES
- %(_python_)s %(cat_py)s $TARGET $SOURCES
- %(_python_)s %(cat_py)s ${TARGET}.yyy $SOURCES yyy
-%(_python_)s %(cat_py)s file3.alt zzz yyy xxx
-%(_python_)s %(cat_py)s file3 zzz yyy xxx
-%(_python_)s %(cat_py)s file3.yyy zzz yyy xxx yyy
-""" % locals())
-
-test.run(chdir='work1/src', arguments=args, stdout=expect)
-
-test.must_match(['work1', 'src', 'file3'], "zzz 2\nyyy 2\nxxx 1\n")
-test.must_match(['work1', 'src', 'file3.alt'], "zzz 2\nyyy 2\nxxx 1\n")
-test.must_match(['work1', 'src', 'file3.yyy'], "zzz 2\nyyy 2\nxxx 1\nyyy 2\n")
-
-#
-test.write(['work1', 'src', 'SConscript'], """\
-Import("env")
-f3 = File('file3')
-env.Cat(f3, ['zzz', 'yyy', 'xxx'])
-env.AddPostAction(f3, r'%(_python_)s %(cat_py)s ${TARGET}.yyy $SOURCES xxx')
-env.AddPreAction(f3, r'%(_python_)s %(cat_py)s ${TARGET}.alt $SOURCES')
-""" % locals())
-
-expect = test.wrap_stdout("""\
-scons: rebuilding `file3' because the build action changed:
- old: %(_python_)s %(cat_py)s ${TARGET}.alt $SOURCES
- %(_python_)s %(cat_py)s $TARGET $SOURCES
- %(_python_)s %(cat_py)s ${TARGET}.yyy $SOURCES yyy
- new: %(_python_)s %(cat_py)s ${TARGET}.alt $SOURCES
- %(_python_)s %(cat_py)s $TARGET $SOURCES
- %(_python_)s %(cat_py)s ${TARGET}.yyy $SOURCES xxx
-%(_python_)s %(cat_py)s file3.alt zzz yyy xxx
-%(_python_)s %(cat_py)s file3 zzz yyy xxx
-%(_python_)s %(cat_py)s file3.yyy zzz yyy xxx xxx
-""" % locals())
-
-test.run(chdir='work1/src', arguments=args, stdout=expect)
-
-test.must_match(['work1', 'src', 'file3'], "zzz 2\nyyy 2\nxxx 1\n")
-test.must_match(['work1', 'src', 'file3.alt'], "zzz 2\nyyy 2\nxxx 1\n")
-test.must_match(['work1', 'src', 'file3.yyy'], "zzz 2\nyyy 2\nxxx 1\nxxx 1\n")
-
-#
-test.write(['work1', 'src', 'SConscript'], """\
-Import("env")
-env.Command('file4', 'file4.in',
- r'%(_python_)s %(cat_py)s $TARGET $FILE4FLAG $SOURCES',
- FILE4FLAG='')
-""" % locals())
-
-expect = test.wrap_stdout("""\
-scons: rebuilding `file4' because the contents of the build action changed
- action: %(_python_)s %(cat_py)s $TARGET $FILE4FLAG $SOURCES
-%(_python_)s %(cat_py)s file4 file4.in
-""" % locals())
-
-test.run(chdir='work1/src',arguments=args, stdout=expect)
-
-test.must_match(['work1', 'src', 'file4'], "file4.in 1\n")
-
-test.up_to_date(chdir='work1/src',arguments='.')
-
-# Test the transition when you turn ON SConsignFile().
-# This will (or might) rebuild things, but we don't care what,
-# we just want to make sure we don't blow up.
-test.write(['work1', 'src', 'SConstruct'],
- "SConsignFile()\n" + SConstruct_contents)
-
-test.run(chdir='work1/src', arguments=args)
-
-test.up_to_date(chdir='work1/src',arguments='.')
-
-#############################################################################
-# Test that the --debug=explain information gets saved by default.
-test.write(['work4', 'src', 'SConstruct'], SConstruct_contents)
-
-test.write(['work4', 'src', 'SConscript'], """\
-Import("env")
-env.Cat('file1', 'file1.in')
-env.Cat('file2', 'file2.k')
-env.Cat('file3', ['xxx', 'yyy', 'zzz'])
-env.Command('file4', 'file4.in', r'%(_python_)s %(cat_py)s $TARGET - $SOURCES')
-env.Cat('file5', 'file5.k')
-env.Cat('subdir/file6', 'subdir/file6.in')
-""" % locals())
-
-test.write(['work4', 'src', 'aaa'], "aaa 1\n")
-test.write(['work4', 'src', 'bbb.k'], """\
-bbb.k 1
-include ccc
-include ../inc/ddd
-include ../inc/eee
-""")
-test.write(['work4', 'src', 'ccc'], "ccc 1\n")
-test.write(['work4', 'src', 'ddd'], "ddd 1\n")
-test.write(['work4', 'src', 'eee.in'], "eee.in 1\n")
-
-test.write(['work4', 'src', 'file1.in'], "file1.in 1\n")
-
-test.write(['work4', 'src', 'file2.k'], """\
-file2.k 1 line 1
-include xxx
-include yyy
-file2.k 1 line 4
-""")
-
-test.write(['work4', 'src', 'file4.in'], "file4.in 1\n")
-
-test.write(['work4', 'src', 'xxx'], "xxx 1\n")
-test.write(['work4', 'src', 'yyy'], "yyy 1\n")
-test.write(['work4', 'src', 'zzz'], "zzz 1\n")
-
-test.write(['work4', 'src', 'file5.k'], """\
-file5.k 1 line 1
-include ../inc/aaa
-include ../inc/bbb.k
-file5.k 1 line 4
-""")
-
-test.write(['work4', 'src', 'subdir', 'file6.in'], "subdir/file6.in 1\n")
-
-#
-test.run(chdir='work4/src', arguments='.')
-
-test.must_match(['work4', 'src', 'file1'], "file1.in 1\n")
-test.must_match(['work4', 'src', 'file2'], """\
-file2.k 1 line 1
-xxx 1
-yyy 1
-file2.k 1 line 4
-""")
-test.must_match(['work4', 'src', 'file3'], "xxx 1\nyyy 1\nzzz 1\n")
-test.must_match(['work4', 'src', 'file4'], "file4.in 1\n")
-test.must_match(['work4', 'src', 'file5'], """\
-file5.k 1 line 1
-aaa 1
-bbb.k 1
-ccc 1
-ddd 1
-eee.in 1
-file5.k 1 line 4
-""")
-
-#
-test.write(['work4', 'src', 'file1.in'], "file1.in 2\n")
-test.write(['work4', 'src', 'yyy'], "yyy 2\n")
-test.write(['work4', 'src', 'zzz'], "zzz 2\n")
-test.write(['work4', 'src', 'bbb.k'], "bbb.k 2\ninclude ccc\n")
-
-work4_inc_bbb_k = test.workpath('work4', 'inc', 'bbb.k')
-work4_inc_ddd = test.workpath('work4', 'inc', 'ddd')
-work4_inc_eee = test.workpath('work4', 'inc', 'eee')
-
-test.run(chdir='work4/src', arguments=args, stdout=test.wrap_stdout("""\
-scons: rebuilding `file1' because `file1.in' changed
-%(_python_)s %(cat_py)s file1 file1.in
-scons: rebuilding `file2' because `yyy' changed
-%(_python_)s %(cat_py)s file2 file2.k
-scons: rebuilding `file3' because:
- `yyy' changed
- `zzz' changed
-%(_python_)s %(cat_py)s file3 xxx yyy zzz
-scons: rebuilding `%(work4_inc_bbb_k)s' because:
- `%(work4_inc_ddd)s' is no longer a dependency
- `%(work4_inc_eee)s' is no longer a dependency
- `bbb.k' changed
-Install file: "bbb.k" as "%(work4_inc_bbb_k)s"
-scons: rebuilding `file5' because `%(work4_inc_bbb_k)s' changed
-%(_python_)s %(cat_py)s file5 file5.k
-""" % locals()))
-
-test.must_match(['work4', 'src', 'file1'], "file1.in 2\n")
-test.must_match(['work4', 'src', 'file2'], """\
-file2.k 1 line 1
-xxx 1
-yyy 2
-file2.k 1 line 4
-""")
-test.must_match(['work4', 'src', 'file3'], "xxx 1\nyyy 2\nzzz 2\n")
-test.must_match(['work4', 'src', 'file5'], """\
-file5.k 1 line 1
-aaa 1
-bbb.k 2
-ccc 1
-file5.k 1 line 4
-""")
-
-
-
-test.write(['work5', 'SConstruct'], """\
-import shutil
-
-env = Environment()
-mode = int(ARGUMENTS.get('mode'))
-if mode:
- def DifferentCopy(target, source, env):
- tgt = str(target[0])
- src = str(source[0])
- shutil.copy(src, tgt)
- def AltCopyStage2(target, source, env):
- pass
- MyCopy = Builder(action = [DifferentCopy, AltCopyStage2])
-
- def ChangingCopy(target, source, env):
- tgt = str(target[0])
- src = str(source[0])
- shutil.copy(src, tgt)
- ChangingCopy = Builder(action = ChangingCopy)
-else:
- MyCopy = Builder(action = Copy('$TARGET', '$SOURCE'))
- def ChangingCopy(target, source, env):
- tgt = str(target[0].abspath)
- src = str(source[0].abspath)
- shutil.copy(src, tgt)
- ChangingCopy = Builder(action = ChangingCopy)
-
-env['BUILDERS']['MyCopy'] = MyCopy
-env['BUILDERS']['ChangingCopy'] = ChangingCopy
-
-env.MyCopy('f1.out', 'f1.in')
-env.ChangingCopy('f2.out', 'f2.in')
-""")
-
-test.write(['work5', 'f1.in'], "work5/f1.in\n")
-test.write(['work5', 'f2.in'], "work5/f2.in\n")
-
-test.run(chdir = 'work5', arguments = "mode=0 .")
-
-test.must_match(['work5', 'f1.out'], "work5/f1.in\n")
-test.must_match(['work5', 'f2.out'], "work5/f2.in\n")
-
-test.run(chdir = 'work5',
- arguments = "--debug=explain mode=1 .",
- stdout = test.wrap_stdout("""\
-scons: rebuilding `f1.out' because the build action changed:
- old: Copy("$TARGET", "$SOURCE")
- new: DifferentCopy(target, source, env)
- AltCopyStage2(target, source, env)
-DifferentCopy(["f1.out"], ["f1.in"])
-AltCopyStage2(["f1.out"], ["f1.in"])
-scons: rebuilding `f2.out' because the contents of the build action changed
- action: ChangingCopy(target, source, env)
-ChangingCopy(["f2.out"], ["f2.in"])
-"""))
-
-
-
-test.pass_test()
diff --git a/test/explain/alias-order.py b/test/explain/alias-order.py
new file mode 100644
index 0000000..78f9642
--- /dev/null
+++ b/test/explain/alias-order.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+#
+# __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__"
+
+"""
+Verify a lot of the basic operation of the --debug=explain option.
+"""
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+args = '--debug=explain target2.dat'
+
+test.subdir('src')
+test.write(['src', 'SConstruct'],"""
+env = Environment()
+
+def action( source, target, env ):
+ f = open( str(target[0]), 'w' )
+ f.write( source[0].get_contents())
+ f.close()
+
+builder = env.Builder( action=action )
+
+builder( env, target = "target1.dat", source = "source1.dat" )
+alias = env.Alias( "alias", "source2.dat" )
+builder( env, target = "target2.dat", source = ["target1.dat"] )
+env.Depends( "target2.dat", alias )
+"""
+)
+
+test.write(["src", "source1.dat"], "a" )
+test.write(["src", "source2.dat"], "a" )
+
+expect = test.wrap_stdout("""\
+scons: building `target1.dat' because it doesn't exist
+action(["target1.dat"], ["source1.dat"])
+scons: building `target2.dat' because it doesn't exist
+action(["target2.dat"], ["target1.dat"])
+""")
+
+test.run(chdir='src', arguments=args, stdout=expect)
+
+test.write(["src", "source1.dat"], "b" )
+
+expect = test.wrap_stdout("""\
+scons: rebuilding `target1.dat' because `source1.dat' changed
+action(["target1.dat"], ["source1.dat"])
+scons: rebuilding `target2.dat' because `target1.dat' changed
+action(["target2.dat"], ["target1.dat"])
+""")
+
+test.run(chdir='src', arguments=args, stdout=expect)
+
+
+test.pass_test()
diff --git a/test/explain/basic.py b/test/explain/basic.py
new file mode 100644
index 0000000..3366359
--- /dev/null
+++ b/test/explain/basic.py
@@ -0,0 +1,399 @@
+#!/usr/bin/env python
+#
+# __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__"
+
+"""
+Verify a lot of the basic operation of the --debug=explain option.
+"""
+
+import os.path
+import string
+import sys
+import TestSCons
+
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+test.subdir(['src'], ['src', 'subdir'])
+
+subdir_file7 = os.path.join('subdir', 'file7')
+subdir_file7_in = os.path.join('subdir', 'file7.in')
+
+cat_py = test.workpath('cat.py')
+inc_aaa = test.workpath('inc', 'aaa')
+inc_ddd = test.workpath('inc', 'ddd')
+inc_eee = test.workpath('inc', 'eee')
+inc_bbb_k = test.workpath('inc', 'bbb.k')
+
+
+
+test.write(cat_py, r"""
+import sys
+
+def process(outfp, infp):
+ for line in infp.readlines():
+ if line[:8] == 'include ':
+ file = line[8:-1]
+ try:
+ fp = open(file, 'rb')
+ except IOError:
+ import os
+ print "os.getcwd() =", os.getcwd()
+ raise
+ process(outfp, fp)
+ else:
+ outfp.write(line)
+
+outfp = open(sys.argv[1], 'wb')
+for f in sys.argv[2:]:
+ if f != '-':
+ process(outfp, open(f, 'rb'))
+
+sys.exit(0)
+""")
+
+SConstruct_contents = """\
+import re
+
+include_re = re.compile(r'^include\s+(\S+)$', re.M)
+
+def kfile_scan(node, env, target, arg):
+ contents = node.get_contents()
+ includes = include_re.findall(contents)
+ return includes
+
+kscan = Scanner(name = 'kfile',
+ function = kfile_scan,
+ argument = None,
+ skeys = ['.k'])
+
+cat = Builder(action = r'%(_python_)s %(cat_py)s $TARGET $SOURCES')
+
+env = Environment()
+env.Append(BUILDERS = {'Cat':cat},
+ SCANNERS = kscan)
+
+Export("env")
+SConscript('SConscript')
+env.Install('../inc', 'aaa')
+env.InstallAs('../inc/bbb.k', 'bbb.k')
+env.Install('../inc', 'ddd')
+env.InstallAs('../inc/eee', 'eee.in')
+""" % locals()
+
+test.write(['src', 'SConstruct'], SConstruct_contents)
+
+test.write(['src', 'SConscript'], """\
+Import("env")
+env.Cat('file1', 'file1.in')
+env.Cat('file2', 'file2.k')
+env.Cat('file3', ['xxx', 'yyy', 'zzz'])
+env.Command('file4', 'file4.in',
+ r'%(_python_)s %(cat_py)s $TARGET $FILE4FLAG $SOURCES',
+ FILE4FLAG='-')
+env.Cat('file5', 'file5.k')
+file6 = env.Cat('file6', 'file6.in')
+AlwaysBuild(file6)
+env.Cat('subdir/file7', 'subdir/file7.in')
+""" % locals())
+
+test.write(['src', 'aaa'], "aaa 1\n")
+test.write(['src', 'bbb.k'], """\
+bbb.k 1
+include ccc
+include ../inc/ddd
+include ../inc/eee
+""")
+test.write(['src', 'ccc'], "ccc 1\n")
+test.write(['src', 'ddd'], "ddd 1\n")
+test.write(['src', 'eee.in'], "eee.in 1\n")
+
+test.write(['src', 'file1.in'], "file1.in 1\n")
+
+test.write(['src', 'file2.k'], """\
+file2.k 1 line 1
+include xxx
+include yyy
+file2.k 1 line 4
+""")
+
+test.write(['src', 'file4.in'], "file4.in 1\n")
+
+test.write(['src', 'xxx'], "xxx 1\n")
+test.write(['src', 'yyy'], "yyy 1\n")
+test.write(['src', 'zzz'], "zzz 1\n")
+
+test.write(['src', 'file5.k'], """\
+file5.k 1 line 1
+include ../inc/aaa
+include ../inc/bbb.k
+file5.k 1 line 4
+""")
+
+test.write(['src', 'file6.in'], "file6.in 1\n")
+
+test.write(['src', 'subdir', 'file7.in'], "subdir/file7.in 1\n")
+
+
+
+args = '--debug=explain .'
+
+
+
+expect = test.wrap_stdout("""\
+scons: building `file1' because it doesn't exist
+%(_python_)s %(cat_py)s file1 file1.in
+scons: building `file2' because it doesn't exist
+%(_python_)s %(cat_py)s file2 file2.k
+scons: building `file3' because it doesn't exist
+%(_python_)s %(cat_py)s file3 xxx yyy zzz
+scons: building `file4' because it doesn't exist
+%(_python_)s %(cat_py)s file4 - file4.in
+scons: building `%(inc_aaa)s' because it doesn't exist
+Install file: "aaa" as "%(inc_aaa)s"
+scons: building `%(inc_ddd)s' because it doesn't exist
+Install file: "ddd" as "%(inc_ddd)s"
+scons: building `%(inc_eee)s' because it doesn't exist
+Install file: "eee.in" as "%(inc_eee)s"
+scons: building `%(inc_bbb_k)s' because it doesn't exist
+Install file: "bbb.k" as "%(inc_bbb_k)s"
+scons: building `file5' because it doesn't exist
+%(_python_)s %(cat_py)s file5 file5.k
+scons: building `file6' because it doesn't exist
+%(_python_)s %(cat_py)s file6 file6.in
+scons: building `%(subdir_file7)s' because it doesn't exist
+%(_python_)s %(cat_py)s %(subdir_file7)s %(subdir_file7_in)s
+""" % locals())
+
+test.run(chdir='src', arguments=args, stdout=expect)
+
+test.must_match(['src', 'file1'], "file1.in 1\n")
+test.must_match(['src', 'file2'], """\
+file2.k 1 line 1
+xxx 1
+yyy 1
+file2.k 1 line 4
+""")
+test.must_match(['src', 'file3'], "xxx 1\nyyy 1\nzzz 1\n")
+test.must_match(['src', 'file4'], "file4.in 1\n")
+test.must_match(['src', 'file5'], """\
+file5.k 1 line 1
+aaa 1
+bbb.k 1
+ccc 1
+ddd 1
+eee.in 1
+file5.k 1 line 4
+""")
+test.must_match(['src', 'file6'], "file6.in 1\n")
+
+
+
+test.write(['src', 'file1.in'], "file1.in 2\n")
+test.write(['src', 'yyy'], "yyy 2\n")
+test.write(['src', 'zzz'], "zzz 2\n")
+test.write(['src', 'bbb.k'], "bbb.k 2\ninclude ccc\n")
+
+expect = test.wrap_stdout("""\
+scons: rebuilding `file1' because `file1.in' changed
+%(_python_)s %(cat_py)s file1 file1.in
+scons: rebuilding `file2' because `yyy' changed
+%(_python_)s %(cat_py)s file2 file2.k
+scons: rebuilding `file3' because:
+ `yyy' changed
+ `zzz' changed
+%(_python_)s %(cat_py)s file3 xxx yyy zzz
+scons: rebuilding `%(inc_bbb_k)s' because:
+ `%(inc_ddd)s' is no longer a dependency
+ `%(inc_eee)s' is no longer a dependency
+ `bbb.k' changed
+Install file: "bbb.k" as "%(inc_bbb_k)s"
+scons: rebuilding `file5' because `%(inc_bbb_k)s' changed
+%(_python_)s %(cat_py)s file5 file5.k
+scons: rebuilding `file6' because AlwaysBuild() is specified
+%(_python_)s %(cat_py)s file6 file6.in
+""" % locals())
+
+test.run(chdir='src', arguments=args, stdout=expect)
+
+test.must_match(['src', 'file1'], "file1.in 2\n")
+test.must_match(['src', 'file2'], """\
+file2.k 1 line 1
+xxx 1
+yyy 2
+file2.k 1 line 4
+""")
+test.must_match(['src', 'file3'], "xxx 1\nyyy 2\nzzz 2\n")
+test.must_match(['src', 'file5'], """\
+file5.k 1 line 1
+aaa 1
+bbb.k 2
+ccc 1
+file5.k 1 line 4
+""")
+
+
+
+test.write(['src', 'SConscript'], """\
+Import("env")
+env.Cat('file3', ['xxx', 'yyy'])
+""")
+
+expect = test.wrap_stdout("""\
+scons: rebuilding `file3' because `zzz' is no longer a dependency
+%(_python_)s %(cat_py)s file3 xxx yyy
+""" % locals())
+
+test.run(chdir='src', arguments=args, stdout=expect)
+
+test.must_match(['src', 'file3'], "xxx 1\nyyy 2\n")
+
+
+
+test.write(['src', 'SConscript'], """\
+Import("env")
+env.Cat('file3', ['xxx', 'yyy', 'zzz'])
+""")
+
+expect = test.wrap_stdout("""\
+scons: rebuilding `file3' because `zzz' is a new dependency
+%(_python_)s %(cat_py)s file3 xxx yyy zzz
+""" % locals())
+
+test.run(chdir='src', arguments=args, stdout=expect)
+
+test.must_match(['src', 'file3'], "xxx 1\nyyy 2\nzzz 2\n")
+
+
+
+test.write(['src', 'SConscript'], """\
+Import("env")
+env.Cat('file3', ['zzz', 'yyy', 'xxx'])
+""")
+
+expect = test.wrap_stdout("""\
+scons: rebuilding `file3' because the dependency order changed:
+ old: ['xxx', 'yyy', 'zzz']
+ new: ['zzz', 'yyy', 'xxx']
+%(_python_)s %(cat_py)s file3 zzz yyy xxx
+""" % locals())
+
+test.run(chdir='src', arguments=args, stdout=expect)
+
+test.must_match(['src', 'file3'], "zzz 2\nyyy 2\nxxx 1\n")
+
+
+
+test.write(['src', 'SConscript'], """\
+Import("env")
+f3 = File('file3')
+env.Cat(f3, ['zzz', 'yyy', 'xxx'])
+env.AddPostAction(f3, r'%(_python_)s %(cat_py)s ${TARGET}.yyy $SOURCES yyy')
+env.AddPreAction(f3, r'%(_python_)s %(cat_py)s ${TARGET}.alt $SOURCES')
+""" % locals())
+
+expect = test.wrap_stdout("""\
+scons: rebuilding `file3' because the build action changed:
+ old: %(_python_)s %(cat_py)s $TARGET $SOURCES
+ new: %(_python_)s %(cat_py)s ${TARGET}.alt $SOURCES
+ %(_python_)s %(cat_py)s $TARGET $SOURCES
+ %(_python_)s %(cat_py)s ${TARGET}.yyy $SOURCES yyy
+%(_python_)s %(cat_py)s file3.alt zzz yyy xxx
+%(_python_)s %(cat_py)s file3 zzz yyy xxx
+%(_python_)s %(cat_py)s file3.yyy zzz yyy xxx yyy
+""" % locals())
+
+test.run(chdir='src', arguments=args, stdout=expect)
+
+test.must_match(['src', 'file3'], "zzz 2\nyyy 2\nxxx 1\n")
+test.must_match(['src', 'file3.alt'], "zzz 2\nyyy 2\nxxx 1\n")
+test.must_match(['src', 'file3.yyy'], "zzz 2\nyyy 2\nxxx 1\nyyy 2\n")
+
+
+
+test.write(['src', 'SConscript'], """\
+Import("env")
+f3 = File('file3')
+env.Cat(f3, ['zzz', 'yyy', 'xxx'])
+env.AddPostAction(f3, r'%(_python_)s %(cat_py)s ${TARGET}.yyy $SOURCES xxx')
+env.AddPreAction(f3, r'%(_python_)s %(cat_py)s ${TARGET}.alt $SOURCES')
+""" % locals())
+
+expect = test.wrap_stdout("""\
+scons: rebuilding `file3' because the build action changed:
+ old: %(_python_)s %(cat_py)s ${TARGET}.alt $SOURCES
+ %(_python_)s %(cat_py)s $TARGET $SOURCES
+ %(_python_)s %(cat_py)s ${TARGET}.yyy $SOURCES yyy
+ new: %(_python_)s %(cat_py)s ${TARGET}.alt $SOURCES
+ %(_python_)s %(cat_py)s $TARGET $SOURCES
+ %(_python_)s %(cat_py)s ${TARGET}.yyy $SOURCES xxx
+%(_python_)s %(cat_py)s file3.alt zzz yyy xxx
+%(_python_)s %(cat_py)s file3 zzz yyy xxx
+%(_python_)s %(cat_py)s file3.yyy zzz yyy xxx xxx
+""" % locals())
+
+test.run(chdir='src', arguments=args, stdout=expect)
+
+test.must_match(['src', 'file3'], "zzz 2\nyyy 2\nxxx 1\n")
+test.must_match(['src', 'file3.alt'], "zzz 2\nyyy 2\nxxx 1\n")
+test.must_match(['src', 'file3.yyy'], "zzz 2\nyyy 2\nxxx 1\nxxx 1\n")
+
+
+
+test.write(['src', 'SConscript'], """\
+Import("env")
+env.Command('file4', 'file4.in',
+ r'%(_python_)s %(cat_py)s $TARGET $FILE4FLAG $SOURCES',
+ FILE4FLAG='')
+""" % locals())
+
+expect = test.wrap_stdout("""\
+scons: rebuilding `file4' because the contents of the build action changed
+ action: %(_python_)s %(cat_py)s $TARGET $FILE4FLAG $SOURCES
+%(_python_)s %(cat_py)s file4 file4.in
+""" % locals())
+
+test.run(chdir='src',arguments=args, stdout=expect)
+
+test.must_match(['src', 'file4'], "file4.in 1\n")
+
+test.up_to_date(chdir='src',arguments='.')
+
+
+
+# Test the transition when you turn ON SConsignFile().
+# This will (or might) rebuild things, but we don't care what,
+# we just want to make sure we don't blow up.
+test.write(['src', 'SConstruct'],
+ "SConsignFile()\n" + SConstruct_contents)
+
+test.run(chdir='src', arguments=args)
+
+test.up_to_date(chdir='src',arguments='.')
+
+
+
+test.pass_test()
diff --git a/test/explain/function-actions.py b/test/explain/function-actions.py
new file mode 100644
index 0000000..b28747d
--- /dev/null
+++ b/test/explain/function-actions.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+#
+# __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__"
+
+"""
+Verify that --debug=explain correctly handles changes to actions
+that contain a list of function Actions.
+"""
+
+import os.path
+import string
+import sys
+import TestSCons
+
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+
+
+test.write('SConstruct', """\
+import shutil
+
+env = Environment()
+mode = int(ARGUMENTS.get('mode'))
+if mode:
+ def DifferentCopy(target, source, env):
+ tgt = str(target[0])
+ src = str(source[0])
+ shutil.copy(src, tgt)
+ def AltCopyStage2(target, source, env):
+ pass
+ MyCopy = Builder(action = [DifferentCopy, AltCopyStage2])
+
+ def ChangingCopy(target, source, env):
+ tgt = str(target[0])
+ src = str(source[0])
+ shutil.copy(src, tgt)
+ ChangingCopy = Builder(action = ChangingCopy)
+else:
+ MyCopy = Builder(action = Copy('$TARGET', '$SOURCE'))
+ def ChangingCopy(target, source, env):
+ tgt = str(target[0].abspath)
+ src = str(source[0].abspath)
+ shutil.copy(src, tgt)
+ ChangingCopy = Builder(action = ChangingCopy)
+
+env['BUILDERS']['MyCopy'] = MyCopy
+env['BUILDERS']['ChangingCopy'] = ChangingCopy
+
+env.MyCopy('f1.out', 'f1.in')
+env.ChangingCopy('f2.out', 'f2.in')
+""")
+
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+
+test.run(arguments = "mode=0 .")
+
+test.must_match('f1.out', "f1.in\n")
+test.must_match('f2.out', "f2.in\n")
+
+test.run(arguments = "--debug=explain mode=1 .",
+ stdout = test.wrap_stdout("""\
+scons: rebuilding `f1.out' because the build action changed:
+ old: Copy("$TARGET", "$SOURCE")
+ new: DifferentCopy(target, source, env)
+ AltCopyStage2(target, source, env)
+DifferentCopy(["f1.out"], ["f1.in"])
+AltCopyStage2(["f1.out"], ["f1.in"])
+scons: rebuilding `f2.out' because the contents of the build action changed
+ action: ChangingCopy(target, source, env)
+ChangingCopy(["f2.out"], ["f2.in"])
+"""))
+
+
+
+test.pass_test()
diff --git a/test/explain/save-info.py b/test/explain/save-info.py
new file mode 100644
index 0000000..0d8f5c7
--- /dev/null
+++ b/test/explain/save-info.py
@@ -0,0 +1,211 @@
+#!/usr/bin/env python
+#
+# __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__"
+
+"""
+Verify that the --debug=explain information gets saved by default.
+"""
+
+import os.path
+import string
+import sys
+import TestSCons
+
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+test.subdir( 'src', ['src', 'subdir'])
+
+cat_py = test.workpath('cat.py')
+inc_bbb_k = test.workpath('inc', 'bbb.k')
+inc_ddd = test.workpath('inc', 'ddd')
+inc_eee = test.workpath('inc', 'eee')
+
+test.write(cat_py, r"""
+import sys
+
+def process(outfp, infp):
+ for line in infp.readlines():
+ if line[:8] == 'include ':
+ file = line[8:-1]
+ try:
+ fp = open(file, 'rb')
+ except IOError:
+ import os
+ print "os.getcwd() =", os.getcwd()
+ raise
+ process(outfp, fp)
+ else:
+ outfp.write(line)
+
+outfp = open(sys.argv[1], 'wb')
+for f in sys.argv[2:]:
+ if f != '-':
+ process(outfp, open(f, 'rb'))
+
+sys.exit(0)
+""")
+
+test.write(['src', 'SConstruct'], """\
+import re
+
+include_re = re.compile(r'^include\s+(\S+)$', re.M)
+
+def kfile_scan(node, env, target, arg):
+ contents = node.get_contents()
+ includes = include_re.findall(contents)
+ return includes
+
+kscan = Scanner(name = 'kfile',
+ function = kfile_scan,
+ argument = None,
+ skeys = ['.k'])
+
+cat = Builder(action = r'%(_python_)s %(cat_py)s $TARGET $SOURCES')
+
+env = Environment()
+env.Append(BUILDERS = {'Cat':cat},
+ SCANNERS = kscan)
+
+Export("env")
+SConscript('SConscript')
+env.Install('../inc', 'aaa')
+env.InstallAs('../inc/bbb.k', 'bbb.k')
+env.Install('../inc', 'ddd')
+env.InstallAs('../inc/eee', 'eee.in')
+""" % locals())
+
+test.write(['src', 'SConscript'], """\
+Import("env")
+env.Cat('file1', 'file1.in')
+env.Cat('file2', 'file2.k')
+env.Cat('file3', ['xxx', 'yyy', 'zzz'])
+env.Command('file4', 'file4.in', r'%(_python_)s %(cat_py)s $TARGET - $SOURCES')
+env.Cat('file5', 'file5.k')
+env.Cat('subdir/file6', 'subdir/file6.in')
+""" % locals())
+
+test.write(['src', 'aaa'], "aaa 1\n")
+test.write(['src', 'bbb.k'], """\
+bbb.k 1
+include ccc
+include ../inc/ddd
+include ../inc/eee
+""")
+test.write(['src', 'ccc'], "ccc 1\n")
+test.write(['src', 'ddd'], "ddd 1\n")
+test.write(['src', 'eee.in'], "eee.in 1\n")
+
+test.write(['src', 'file1.in'], "file1.in 1\n")
+
+test.write(['src', 'file2.k'], """\
+file2.k 1 line 1
+include xxx
+include yyy
+file2.k 1 line 4
+""")
+
+test.write(['src', 'file4.in'], "file4.in 1\n")
+
+test.write(['src', 'xxx'], "xxx 1\n")
+test.write(['src', 'yyy'], "yyy 1\n")
+test.write(['src', 'zzz'], "zzz 1\n")
+
+test.write(['src', 'file5.k'], """\
+file5.k 1 line 1
+include ../inc/aaa
+include ../inc/bbb.k
+file5.k 1 line 4
+""")
+
+test.write(['src', 'subdir', 'file6.in'], "subdir/file6.in 1\n")
+
+#
+test.run(chdir='src', arguments='.')
+
+test.must_match(['src', 'file1'], "file1.in 1\n")
+test.must_match(['src', 'file2'], """\
+file2.k 1 line 1
+xxx 1
+yyy 1
+file2.k 1 line 4
+""")
+test.must_match(['src', 'file3'], "xxx 1\nyyy 1\nzzz 1\n")
+test.must_match(['src', 'file4'], "file4.in 1\n")
+test.must_match(['src', 'file5'], """\
+file5.k 1 line 1
+aaa 1
+bbb.k 1
+ccc 1
+ddd 1
+eee.in 1
+file5.k 1 line 4
+""")
+
+test.write(['src', 'file1.in'], "file1.in 2\n")
+test.write(['src', 'yyy'], "yyy 2\n")
+test.write(['src', 'zzz'], "zzz 2\n")
+test.write(['src', 'bbb.k'], "bbb.k 2\ninclude ccc\n")
+
+expect = test.wrap_stdout("""\
+scons: rebuilding `file1' because `file1.in' changed
+%(_python_)s %(cat_py)s file1 file1.in
+scons: rebuilding `file2' because `yyy' changed
+%(_python_)s %(cat_py)s file2 file2.k
+scons: rebuilding `file3' because:
+ `yyy' changed
+ `zzz' changed
+%(_python_)s %(cat_py)s file3 xxx yyy zzz
+scons: rebuilding `%(inc_bbb_k)s' because:
+ `%(inc_ddd)s' is no longer a dependency
+ `%(inc_eee)s' is no longer a dependency
+ `bbb.k' changed
+Install file: "bbb.k" as "%(inc_bbb_k)s"
+scons: rebuilding `file5' because `%(inc_bbb_k)s' changed
+%(_python_)s %(cat_py)s file5 file5.k
+""" % locals())
+
+test.run(chdir='src', arguments='--debug=explain .', stdout=expect)
+
+test.must_match(['src', 'file1'], "file1.in 2\n")
+test.must_match(['src', 'file2'], """\
+file2.k 1 line 1
+xxx 1
+yyy 2
+file2.k 1 line 4
+""")
+test.must_match(['src', 'file3'], "xxx 1\nyyy 2\nzzz 2\n")
+test.must_match(['src', 'file5'], """\
+file5.k 1 line 1
+aaa 1
+bbb.k 2
+ccc 1
+file5.k 1 line 4
+""")
+
+
+
+test.pass_test()
diff --git a/test/import.py b/test/import.py
index 7debffc..a26d745 100644
--- a/test/import.py
+++ b/test/import.py
@@ -179,8 +179,8 @@ if moc:
qtdir = os.path.dirname(os.path.dirname(moc))
- qt_err = """
-scons: warning: Could not detect qt, using moc executable as a hint (QTDIR=%(qtdir)s)
+ qt_err = r"""
+scons: warning: Could not detect qt, using moc executable as a hint \(QTDIR=%(qtdir)s\)
""" % locals()
else:
diff --git a/test/option/debug-count.py b/test/option/debug-count.py
index 3a0fbb0..d301312 100644
--- a/test/option/debug-count.py
+++ b/test/option/debug-count.py
@@ -83,5 +83,15 @@ for args in ['-h --debug=count', '--debug=count']:
print stdout
test.fail_test(1)
+expect_warning = """
+scons: warning: --debug=count is not supported when running SCons
+\twith the python -O option or optimized \\(.pyo\\) modules.
+""" + TestSCons.file_expr
+
+test.run(arguments = '--debug=count -h',
+ interpreter = ['python', '-O'],
+ stderr = expect_warning,
+ match = TestSCons.match_re)
+
test.pass_test()
diff --git a/test/option/debug-stacktrace.py b/test/option/debug-stacktrace.py
index aff25f2..cf7b81d 100644
--- a/test/option/debug-stacktrace.py
+++ b/test/option/debug-stacktrace.py
@@ -51,7 +51,7 @@ def must_contain_all_lines(content, lines):
test.write('SConstruct', """\
def kfile_scan(node, env, target):
- raise "kfile_scan error"
+ raise Exception, "kfile_scan error"
kscan = Scanner(name = 'kfile',
function = kfile_scan,
@@ -74,7 +74,7 @@ test.run(arguments = "--debug=stacktrace",
lines = [
"scons: *** kfile_scan error",
"scons: internal stack trace:",
- 'raise "kfile_scan error"',
+ 'raise Exception, "kfile_scan error"',
]
err = must_contain_all_lines(test.stderr(), lines)
diff --git a/test/option/debug-time.py b/test/option/debug-time.py
index 51cfacf..bf18b97 100644
--- a/test/option/debug-time.py
+++ b/test/option/debug-time.py
@@ -70,6 +70,18 @@ def num(s, match):
def within_tolerance(expected, actual, tolerance):
return abs((expected-actual)/actual) <= tolerance
+def get_total_time(stdout):
+ return num(stdout, r'Total build time: (\d+\.\d+) seconds')
+
+def get_sconscript_time(stdout):
+ return num(stdout, r'Total SConscript file execution time: (\d+\.\d+) seconds')
+
+def get_scons_time(stdout):
+ return num(stdout, r'Total SCons execution time: (\d+\.\d+) seconds')
+
+def get_command_time(stdout):
+ return num(stdout, r'Total command execution time: (\d+\.\d+) seconds')
+
# Try to make our results a little more accurate and repeatable by
@@ -99,10 +111,10 @@ expected_command_time = reduce(lambda x, y: x + y, times, 0.0)
stdout = test.stdout()
-total_time = num(stdout, r'Total build time: (\d+\.\d+) seconds')
-sconscript_time = num(stdout, r'Total SConscript file execution time: (\d+\.\d+) seconds')
-scons_time = num(stdout, r'Total SCons execution time: (\d+\.\d+) seconds')
-command_time = num(stdout, r'Total command execution time: (\d+\.\d+) seconds')
+total_time = get_total_time(stdout)
+sconscript_time = get_sconscript_time(stdout)
+scons_time = get_scons_time(stdout)
+command_time = get_command_time(stdout)
failures = []
@@ -133,6 +145,13 @@ if failures:
print string.join([test.stdout()] + failures, '\n')
test.fail_test(1)
+test.run(arguments = "--debug=time . SLEEP=0")
+
+command_time = get_command_time(test.stdout())
+if command_time != 0.0:
+ print "Up-to-date run received non-zero command time of %s" % command_time
+ test.fail_test()
+
test.run(arguments = "-c")
@@ -143,10 +162,10 @@ test.run(arguments = "-j4 --debug=time . SLEEP=1")
stdout = test.stdout()
-total_time = num(stdout, r'Total build time: (\d+\.\d+) seconds')
-sconscript_time = num(stdout, r'Total SConscript file execution time: (\d+\.\d+) seconds')
-scons_time = num(stdout, r'Total SCons execution time: (\d+\.\d+) seconds')
-command_time = num(stdout, r'Total command execution time: (\d+\.\d+) seconds')
+total_time = get_total_time(stdout)
+sconscript_time = get_sconscript_time(stdout)
+scons_time = get_scons_time(stdout)
+command_time = get_command_time(stdout)
failures = []
@@ -162,5 +181,12 @@ if failures:
print string.join([test.stdout()] + failures, '\n')
test.fail_test(1)
+test.run(arguments = "-j4 --debug=time . SLEEP=1")
+
+command_time = get_command_time(test.stdout())
+if command_time != 0.0:
+ print "Up-to-date run received non-zero command time of %s" % command_time
+ test.fail_test()
+
test.pass_test()
diff --git a/test/option/profile.py b/test/option/profile.py
index eb6b394..7adf933 100644
--- a/test/option/profile.py
+++ b/test/option/profile.py
@@ -49,13 +49,13 @@ test.run(arguments = "--profile=%s -h" % scons_prof)
test.fail_test(string.find(test.stdout(), 'usage: scons [OPTION]') == -1)
test.fail_test(string.find(test.stdout(), 'usage: scons [OPTION]') == -1)
-stats = pstats.Stats(scons_prof)
-stats.sort_stats('time')
-
try:
save_stdout = sys.stdout
sys.stdout = StringIO.StringIO()
+ stats = pstats.Stats(scons_prof)
+ stats.sort_stats('time')
+
stats.strip_dirs().print_stats()
s = sys.stdout.getvalue()
@@ -63,9 +63,7 @@ finally:
sys.stdout = save_stdout
test.fail_test(string.find(s, 'Main.py') == -1)
-test.fail_test(string.find(s, 'print_help') == -1)
test.fail_test(string.find(s, '_main') == -1)
-test.fail_test(string.find(s, 'option_parser.py') == -1)
@@ -73,13 +71,13 @@ scons_prof = test.workpath('scons2.prof')
test.run(arguments = "--profile %s" % scons_prof)
-stats = pstats.Stats(scons_prof)
-stats.sort_stats('time')
-
try:
save_stdout = sys.stdout
sys.stdout = StringIO.StringIO()
+ stats = pstats.Stats(scons_prof)
+ stats.sort_stats('time')
+
stats.strip_dirs().print_stats()
s = sys.stdout.getvalue()