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