From b3a9ef543300fbf7fc2f83d46adec2cc841cecc8 Mon Sep 17 00:00:00 2001 From: stevenknight Date: Wed, 11 Jul 2007 11:26:34 +0000 Subject: [PATCH] Merged revisions 1968-2115 via svnmerge from http://scons.tigris.org/svn/scons/branches/core ........ r1970 | stevenknight | 2007-06-01 18:22:37 -0500 (Fri, 01 Jun 2007) | 4 lines Import a vanilla Python 2.3 version of textwrap.py into the compatibility library, so we can track the changes we'll make to it. (This isn't actually used yet.) ........ r1971 | stevenknight | 2007-06-02 00:38:20 -0500 (Sat, 02 Jun 2007) | 2 lines Add a compatibility module for the textwrap.py module introduced in Python 2.3. ........ r1972 | stevenknight | 2007-06-02 00:39:26 -0500 (Sat, 02 Jun 2007) | 2 lines Remove spurious tags. ........ r1973 | stevenknight | 2007-06-03 08:57:05 -0500 (Sun, 03 Jun 2007) | 2 lines Improved help-text generation using a textwrap.TextWrapper object. ........ r1991 | stevenknight | 2007-06-10 16:03:18 -0500 (Sun, 10 Jun 2007) | 3 lines Add compatibility versions of the all() and any() functions introduced in Python 2.5. ........ r1992 | stevenknight | 2007-06-10 17:02:18 -0500 (Sun, 10 Jun 2007) | 8 lines SCons-time portability fixes for Python 2.[12]: -- Use "from __future__ import nested_scopes". -- Create "False" and "True" builtins -- Work around the lack of a "prefix =" keyword argument to the Python 2.[12] version of the mktemp module. -- Accomodate pickier single-element tuple syntax. ........ r1993 | stevenknight | 2007-06-10 17:27:43 -0500 (Sun, 10 Jun 2007) | 3 lines Delay instantiation of pstat.Stats objects until after we override sys.stdout, which as of Python 2.5 is captured when the object is created. ........ r1994 | stevenknight | 2007-06-10 21:22:42 -0500 (Sun, 10 Jun 2007) | 6 lines Update various tests to handle the File "SConstruct", line 1, in Messages in Python 2.5. ........ r1995 | stevenknight | 2007-06-10 21:32:16 -0500 (Sun, 10 Jun 2007) | 3 lines Update tests to not raise strings as exceptions, which has been deprecated in Python 2.5. ........ r1996 | stevenknight | 2007-06-10 21:41:57 -0500 (Sun, 10 Jun 2007) | 3 lines Fix the Scanner hash unit test for Python 2.5. (Yes, it still works on previous versions, too.) ........ r1997 | stevenknight | 2007-06-10 21:55:46 -0500 (Sun, 10 Jun 2007) | 3 lines Make the mock Node object's side_effect attribute a list, so it's iterable in Python 2.1 as well. ........ r1998 | stevenknight | 2007-06-10 22:04:26 -0500 (Sun, 10 Jun 2007) | 3 lines Append an explicit tuple to the delayed_warnings list if there are problems interpreting --debug=memoizer. ........ r1999 | stevenknight | 2007-06-11 11:09:07 -0500 (Mon, 11 Jun 2007) | 2 lines Fix --debug=time with -j when no arguments are rebuilt (all up-to-date). ........ r2007 | stevenknight | 2007-06-14 13:56:35 -0500 (Thu, 14 Jun 2007) | 4 lines Performance improvement when looking up Nodes: don't use is_String(), just check for the initial '#' that specifies a top-relative lookup, and handle the exceptions. ........ r2008 | stevenknight | 2007-06-14 16:57:47 -0500 (Thu, 14 Jun 2007) | 11 lines First step in refactoring command-line flag processing: Split out the current processing into its own module, with minimal additional changes. Among the minimal changes: -- Store delayed warnings (for deprecated --debug= keywords) in the option parser object, not in a global variable. -- Remove the OptParser variable itself from the SCons.Script globals. It's going to change significantly (and no one's probably using it anyway). -- Don't move definition of the --version output with the OptParser, keep it in Main.py. ........ r2009 | stevenknight | 2007-06-15 08:15:25 -0500 (Fri, 15 Jun 2007) | 3 lines Refactor the test/explain.py script into three individual scripts so it's easier to deal with. ........ r2010 | stevenknight | 2007-06-15 09:49:07 -0500 (Fri, 15 Jun 2007) | 3 lines Handle Aliases in --debug=explain. This is kind of hard-coded for the normal lookup, and should be better handled by the signature refactoring. ........ r2011 | stevenknight | 2007-06-15 17:25:37 -0500 (Fri, 15 Jun 2007) | 5 lines Refactor use of the command-line parser object so it's localized to the top-level main() function, and not passed down through _exec_main() or to _main() itself. Replace its functionality with use of an exception to signal that the top-level main() function should print its help message. ........ r2012 | stevenknight | 2007-06-17 23:34:26 -0500 (Sun, 17 Jun 2007) | 2 lines Remove unnecessary import of __main__. ........ r2013 | stevenknight | 2007-06-17 23:48:06 -0500 (Sun, 17 Jun 2007) | 2 lines Pass the options object to _main(), don't use a global. ........ r2014 | stevenknight | 2007-06-18 00:12:09 -0500 (Mon, 18 Jun 2007) | 6 lines Qt test fixes for Windows: Link against a static library created by the test infrastructure, not a shared library. Escape backslashes in Windows path names. Skip test/QT/Tool.py if Qt isn't installed. ........ r2015 | stevenknight | 2007-06-18 10:46:17 -0500 (Mon, 18 Jun 2007) | 3 lines Support GetOption('no_exec'), and update test/NodeOps.py to use it instead of reaching into the SCons.Script.Main internals. ........ r2016 | stevenknight | 2007-06-18 11:04:39 -0500 (Mon, 18 Jun 2007) | 4 lines Restore use of a global delayed_warnings variable so the chicken-and-egg warning from trying to use --debug=memoizer on Python versions without metaclasses has somewhere to go. ........ r2017 | stevenknight | 2007-06-18 11:37:59 -0500 (Mon, 18 Jun 2007) | 3 lines Have the test infrastructure create a mock Qt shared library on UNIX, static library on Windows. ........ r2018 | stevenknight | 2007-06-18 11:48:10 -0500 (Mon, 18 Jun 2007) | 2 lines Pull more globals into the command-line parser options object. ........ r2023 | stevenknight | 2007-06-19 16:46:02 -0500 (Tue, 19 Jun 2007) | 3 lines Refactor the __checkClass() and must_be_a_Dir() methods into a more general and more efficient must_be_same() method. ........ r2025 | stevenknight | 2007-06-19 20:56:10 -0500 (Tue, 19 Jun 2007) | 3 lines More clean up: change various self.fs.Entry() calls to calls through the bound directory.Entry() method. ........ r2033 | stevenknight | 2007-06-20 20:03:23 -0500 (Wed, 20 Jun 2007) | 5 lines The --debug=count option doesn't work when run with Python - O, or from optimized compiled Python modules (*.pyo files), because the counting is all within "#if __debug__:" blocks that get stripped. Print a warning so it doesn't look like --debug=count is broken. ........ r2037 | stevenknight | 2007-06-21 10:42:40 -0500 (Thu, 21 Jun 2007) | 3 lines Replace the _stripixes() function with a more efficient/readable version that was checked in, but commented out, prior to 0.96.96. ........ r2040 | stevenknight | 2007-06-21 12:18:57 -0500 (Thu, 21 Jun 2007) | 2 lines Ignore *.pyo files, too, since one of the tests now generates them. ........ r2051 | stevenknight | 2007-06-26 15:11:57 -0500 (Tue, 26 Jun 2007) | 5 lines Arrange for graceful shutdown of the worker threads by writing None to the requestQueue and having the worker threads terminate their processing loops when they read it. We can then .join() the threads, to wait for their termination, avoiding exceptions in the threading library module. ........ r2052 | stevenknight | 2007-06-26 15:12:53 -0500 (Tue, 26 Jun 2007) | 3 lines Have the SWIG tests that use the Python.h header skip gracefully if the Python development environment isn't installed. ........ r2053 | stevenknight | 2007-06-26 15:23:55 -0500 (Tue, 26 Jun 2007) | 3 lines Massage the datestamp and IDs in the generated PDF so we can compare before-and-after output reliably regardless of when generated. ........ r2054 | stevenknight | 2007-06-26 15:25:56 -0500 (Tue, 26 Jun 2007) | 3 lines Fix the regular expression that matches the Qt warning message when the moc executable is used as a hint. ........ r2055 | stevenknight | 2007-06-26 15:48:21 -0500 (Tue, 26 Jun 2007) | 2 lines Fix 2.5.1 string exception warnings. ........ r2056 | stevenknight | 2007-06-26 19:23:22 -0500 (Tue, 26 Jun 2007) | 2 lines Skip the scons-time tests if the Python version can't import __future__. ........ r2057 | stevenknight | 2007-06-26 22:11:04 -0500 (Tue, 26 Jun 2007) | 3 lines Normalize PDF output in the bibtex rerun test as well. Commonize the PDF normalization logic by putting it in QMTest/TestSCons.py. ........ r2058 | stevenknight | 2007-06-26 22:50:39 -0500 (Tue, 26 Jun 2007) | 3 lines Duplicate a function declaration to suppress compiler warnings about a cast, when using certain systems/compilers. ........ r2059 | stevenknight | 2007-06-26 22:53:12 -0500 (Tue, 26 Jun 2007) | 2 lines Use the frtbegin when compiling Fortran programs using GCC 4. ........ r2060 | stevenknight | 2007-06-26 23:13:35 -0500 (Tue, 26 Jun 2007) | 2 lines Make the object that goes into the shared library a shared object file. ........ r2061 | stevenknight | 2007-06-26 23:53:49 -0500 (Tue, 26 Jun 2007) | 4 lines Split test/AS/AS.py into sub-tests for the live assemblers it tests. Only test nasm for the known configuration of version 0.98* on a 32-bit x86 system. ........ r2063 | stevenknight | 2007-06-27 09:51:43 -0500 (Wed, 27 Jun 2007) | 2 lines Fix searching for the rmic utility. ........ r2064 | stevenknight | 2007-06-27 10:26:42 -0500 (Wed, 27 Jun 2007) | 3 lines Improved worker-thread termination in a separate Job.cleanup() method. (Adam Simpkins) ........ r2087 | stevenknight | 2007-07-03 12:22:10 -0500 (Tue, 03 Jul 2007) | 7 lines Get rid of unnecessary subclassing and complicating overriding of __init__() and parse_args() methods in favor of more straightforward initialization of the OptionParser object. We may need to restore subclassing in the future, but if so we'll do it in a more OO way. ........ r2088 | stevenknight | 2007-07-03 16:12:30 -0500 (Tue, 03 Jul 2007) | 2 lines Fix a cleanup error (no self.p4d attribute) when Perforce isn't installed. ........ r2090 | stevenknight | 2007-07-04 03:23:57 -0500 (Wed, 04 Jul 2007) | 2 lines Import the vanilla Python 2.5 optparse.py for use as a compatibility module. ........ r2091 | stevenknight | 2007-07-04 03:35:17 -0500 (Wed, 04 Jul 2007) | 5 lines Use the new optparse compatibility module for command-line processing, and remove the SCons/Optik/*.py modules, with appropriate subclassing in Script/SConsOptions.py to preserve the way we print help text and SCons error messages. ........ r2108 | stevenknight | 2007-07-08 22:57:08 -0500 (Sun, 08 Jul 2007) | 3 lines Make all of the optparse.add_options calls more-or-less consistent in how they call the keyword arguments. ........ r2109 | stevenknight | 2007-07-09 12:31:01 -0500 (Mon, 09 Jul 2007) | 6 lines Consolidate command-line and {Get,Set}Option() processing and access in a single subclass of the optparse.Values() class. Allow all options, not just those that aren't SConscript-settable, to set their default values when calling op.add_option(). ........ r2110 | stevenknight | 2007-07-09 13:17:58 -0500 (Mon, 09 Jul 2007) | 4 lines Handle initialization of command-line repository options by passing the option arguments directly to the _SConstruct_exists() utility function, not by setting a global variable. ........ r2111 | stevenknight | 2007-07-09 13:42:41 -0500 (Mon, 09 Jul 2007) | 2 lines Remove the unused _varargs() utility function. ........ r2112 | stevenknight | 2007-07-09 15:21:51 -0500 (Mon, 09 Jul 2007) | 2 lines Clean up how we use optparse (mainly for readability). ........ r2113 | stevenknight | 2007-07-10 15:50:08 -0500 (Tue, 10 Jul 2007) | 2 lines More old-Python-version compatibility changes in optparse.py. ........ r2114 | stevenknight | 2007-07-10 16:46:42 -0500 (Tue, 10 Jul 2007) | 3 lines Add support for a new AddOption() function to allow the SConscript file(s) to define new command-line flags. ........ git-svn-id: http://scons.tigris.org/svn/scons/trunk@2118 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- QMTest/TestSCons.py | 21 +- QMTest/TestSCons_time.py | 13 + doc/man/scons.1 | 90 +- doc/user/libraries.in | 2 - doc/user/libraries.sgml | 2 - src/CHANGES.txt | 11 + src/engine/MANIFEST.in | 7 +- src/engine/SCons/Defaults.py | 119 +- src/engine/SCons/Environment.py | 2 +- src/engine/SCons/Job.py | 49 +- src/engine/SCons/JobTests.py | 8 +- src/engine/SCons/Node/FS.py | 100 +- src/engine/SCons/Node/FSTests.py | 2 +- src/engine/SCons/Optik/__init__.py | 32 - src/engine/SCons/Optik/errors.py | 55 - src/engine/SCons/Optik/option.py | 388 ----- src/engine/SCons/Optik/option_parser.py | 730 --------- src/engine/SCons/SConfTests.py | 2 +- src/engine/SCons/Scanner/ScannerTests.py | 6 +- src/engine/SCons/Script/Main.py | 688 ++------ src/engine/SCons/Script/MainTests.py | 30 +- src/engine/SCons/Script/SConsOptions.py | 819 ++++++++++ src/engine/SCons/Script/SConscript.py | 4 +- src/engine/SCons/Script/__init__.py | 23 +- src/engine/SCons/Warnings.py | 3 + src/engine/SCons/compat/__init__.py | 14 + src/engine/SCons/compat/_scons_optparse.py | 1719 ++++++++++++++++++++ src/engine/SCons/compat/_scons_textwrap.py | 373 +++++ src/engine/SCons/compat/builtins.py | 32 + src/script/scons-time.py | 62 +- test/AS/AS.py | 317 +--- test/AS/as-live.py | 184 +++ test/AS/ml.py | 124 ++ test/AS/nasm.py | 149 ++ test/AddOption/basic.py | 70 + test/AddOption/help.py | 82 + test/Configure/ConfigureDryRunError.py | 16 +- test/Configure/option--config.py | 9 +- test/Java/RMIC.py | 2 +- test/LIBPATH.py | 4 +- test/NodeOps.py | 4 +- test/Options/BoolOption.py | 3 +- test/Options/EnumOption.py | 9 +- test/Options/ListOption.py | 15 +- test/Options/PackageOption.py | 3 +- test/Options/PathOption.py | 21 +- test/Perforce/Perforce.py | 2 + test/QT/Tool.py | 8 +- test/QT/warnings.py | 6 +- test/RCS/diskcheck.py | 8 +- test/Repository/RMIC.py | 2 +- test/SCCS/diskcheck.py | 11 +- test/SWIG/live.py | 23 +- test/SWIG/remove-modules.py | 9 + test/Scanner/exception.py | 2 +- test/Script-import.py | 45 +- test/TEX/auxiliaries.py | 16 +- test/TEX/bibtex-latex-rerun.py | 14 +- test/bad-variables.py | 7 +- test/compat/all.py | 59 + test/compat/any.py | 57 + test/exceptions.py | 6 +- test/explain.py | 561 ------- test/explain/alias-order.py | 81 + test/explain/basic.py | 399 +++++ test/explain/function-actions.py | 100 ++ test/explain/save-info.py | 211 +++ test/import.py | 4 +- test/option/debug-count.py | 10 + test/option/debug-stacktrace.py | 4 +- test/option/debug-time.py | 42 +- test/option/profile.py | 14 +- 72 files changed, 5200 insertions(+), 2919 deletions(-) delete mode 100644 src/engine/SCons/Optik/__init__.py delete mode 100644 src/engine/SCons/Optik/errors.py delete mode 100644 src/engine/SCons/Optik/option.py delete mode 100644 src/engine/SCons/Optik/option_parser.py create mode 100644 src/engine/SCons/Script/SConsOptions.py create mode 100644 src/engine/SCons/compat/_scons_optparse.py create mode 100644 src/engine/SCons/compat/_scons_textwrap.py create mode 100644 test/AS/as-live.py create mode 100644 test/AS/ml.py create mode 100644 test/AS/nasm.py create mode 100644 test/AddOption/basic.py create mode 100644 test/AddOption/help.py create mode 100644 test/compat/all.py create mode 100644 test/compat/any.py delete mode 100644 test/explain.py create mode 100644 test/explain/alias-order.py create mode 100644 test/explain/basic.py create mode 100644 test/explain/function-actions.py create mode 100644 test/explain/save-info.py diff --git a/QMTest/TestSCons.py b/QMTest/TestSCons.py index 8d820b60..e1884acc 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 [ ]', 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 34caa062..1ceb5298 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 c0b91ecd..769cfc27 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 @@ -2026,6 +2034,84 @@ env.AddMethod(my_method, 'other_method_name') 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 ) @@ -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 9a120622..0fe53b88 100644 --- a/doc/user/libraries.in +++ b/doc/user/libraries.in @@ -108,8 +108,6 @@ - - Library('foo', ['f1.c', 'f2.o', 'f3.c', 'f4.o']) diff --git a/doc/user/libraries.sgml b/doc/user/libraries.sgml index 96814a70..772810fd 100644 --- a/doc/user/libraries.sgml +++ b/doc/user/libraries.sgml @@ -106,8 +106,6 @@ - - Library('foo', ['f1.c', 'f2.o', 'f3.c', 'f4.o']) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 9cf69e90..70519bfb 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 3905a91d..4bcd1e09 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 23d02e94..136c3f7b 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 94cdf740..bffe0ef2 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 9832f146..b28aaaff 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 e38e2511..5a47da72 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 c9a1443f..152a3897 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 b9971a2f..0b60fb96 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 8ea41fb4..00000000 --- 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 - -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 dca8a698..00000000 --- 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 7493205d..00000000 --- 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 49b1874e..00000000 --- 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 22ec188b..d4ed3460 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 30dc1dfd..ae77908b 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 d80050d0..53e51296 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 or no-. @@ -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 aaf14826..fe32df91 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 00000000..053fff1f --- /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 d25e44c6..7326c0c8 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 e9709895..8f97c418 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 b313ee06..b1d39ec6 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 959bf52d..0dfb18e0 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 00000000..6b376875 --- /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 + +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 00000000..1e0e4118 --- /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 + +__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 1124cf0f..a4c3080c 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 @@ -57,6 +59,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: diff --git a/src/script/scons-time.py b/src/script/scons-time.py index b0feaab2..284443a4 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 8bfa7402..e8a2a595 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 -.file STRING -.data -.align 4 -.globl name -name: - .ascii STRING - .byte 0 -""") - - test.write('aaa_main.c', r""" -#include -#include - -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 -#include - -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 -#include - -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 00000000..886dc8dd --- /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 +.file STRING +.data +.align 4 +.globl name +name: + .ascii STRING + .byte 0 +""") + +test.write('aaa_main.c', r""" +#include +#include + +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 +#include + +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 +#include + +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 00000000..0cb0e9ec --- /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 00000000..f96db7e6 --- /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 +#include + +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 00000000..57ee11e2 --- /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 00000000..d7f02f19 --- /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 04fbc751..65c64038 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 95a0d3b7..807ddb55 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 a66f885a..2ab1804e 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 1ea7ed8f..7fd4f9bd 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 17a0f02a..38692287 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 aab2b205..38b30d5d 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 5e29477a..610a4854 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 fddfc5f3..b6ba3061 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 d285338e..79d4dda7 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 d22071eb..23fec6e5 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 a24c65da..b409210e 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 9079225f..3ccabf7f 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 0fd3f663..baa1e6fa 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 (\?|) """ % (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 (\?|) """ % 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 78120f69..07cb1707 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 e4a44599..ebe6a83d 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 66566fad..a9df89b8 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 c6b45d22..93336ca1 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 cac96779..1a48c9ee 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 7615888b..d8204dba 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 544960bb..3df980ff 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 e34ddaa0..39984b3b 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 353d9a42..a0ba5c2b 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 cb6b357c..0618c5eb 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 00000000..ee8f9ef0 --- /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 00000000..cab191cf --- /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 01a28552..34c24f45 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 9b721d2b..00000000 --- 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 00000000..78f96426 --- /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 00000000..3366359a --- /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 00000000..b28747dc --- /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 00000000..0d8f5c79 --- /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 7debffc3..a26d745f 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 3a0fbb03..d3013120 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 aff25f24..cf7b81d2 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 51cfacfb..bf18b97d 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 eb6b3945..7adf9337 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() -- 2.26.2