diff options
Diffstat (limited to 'src/engine')
66 files changed, 1451 insertions, 308 deletions
diff --git a/src/engine/MANIFEST.in b/src/engine/MANIFEST.in index 029217e..2484877 100644 --- a/src/engine/MANIFEST.in +++ b/src/engine/MANIFEST.in @@ -2,6 +2,8 @@ SCons/__init__.py SCons/Action.py SCons/Builder.py SCons/compat/__init__.py +SCons/compat/_sets.py +SCons/compat/_sets15.py SCons/compat/_subprocess.py SCons/compat/_UserString.py SCons/compat/builtins.py diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index 503dc9f..dd7009c 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -100,12 +100,12 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import dis import os import os.path -import re import string import sys from SCons.Debug import logInstanceCreation import SCons.Errors +import SCons.Executor import SCons.Util class _Null: @@ -403,7 +403,8 @@ class CommandAction(_ActionAction): def strfunction(self, target, source, env): if not self.cmdstr is None: - c = env.subst(self.cmdstr, SCons.Subst.SUBST_RAW, target, source) + from SCons.Subst import SUBST_RAW + c = env.subst(self.cmdstr, SUBST_RAW, target, source) if c: return c cmd_list, ignore, silent = self.process(target, source, env) @@ -421,7 +422,10 @@ class CommandAction(_ActionAction): externally. """ from SCons.Subst import escape_list - from SCons.Util import is_String, is_List, flatten + import SCons.Util + flatten = SCons.Util.flatten + is_String = SCons.Util.is_String + is_List = SCons.Util.is_List try: shell = env['SHELL'] @@ -432,6 +436,9 @@ class CommandAction(_ActionAction): spawn = env['SPAWN'] except KeyError: raise SCons.Errors.UserError('Missing SPAWN construction variable.') + else: + if is_String(spawn): + spawn = env.subst(spawn, raw=1, conv=lambda x: x) escape = env.get('ESCAPE', lambda x: x) @@ -618,7 +625,8 @@ class FunctionAction(_ActionAction): if self.cmdstr is None: return None if not self.cmdstr is _null: - c = env.subst(self.cmdstr, SCons.Subst.SUBST_RAW, target, source) + from SCons.Subst import SUBST_RAW + c = env.subst(self.cmdstr, SUBST_RAW, target, source) if c: return c def array(a): diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index 2c6d915..8339610 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -135,11 +135,13 @@ class Environment: for k, v in kw.items(): self.d[k] = v # Just use the underlying scons_subst*() utility methods. - def subst(self, strSubst, raw=0, target=[], source=[]): - return SCons.Subst.scons_subst(strSubst, self, raw, target, source, self.d) + def subst(self, strSubst, raw=0, target=[], source=[], conv=None): + return SCons.Subst.scons_subst(strSubst, self, raw, + target, source, self.d, conv=conv) subst_target_source = subst - def subst_list(self, strSubst, raw=0, target=[], source=[]): - return SCons.Subst.scons_subst_list(strSubst, self, raw, target, source, self.d) + def subst_list(self, strSubst, raw=0, target=[], source=[], conv=None): + return SCons.Subst.scons_subst_list(strSubst, self, raw, + target, source, self.d, conv=conv) def __getitem__(self, item): return self.d[item] def __setitem__(self, item, value): @@ -1151,6 +1153,11 @@ class CommandActionTestCase(unittest.TestCase): assert t.executed == [ 'xyzzy' ], t.executed a = SCons.Action.CommandAction(["xyzzy"]) + e = Environment(SPAWN = '$FUNC', FUNC = func) + a([], [], e) + assert t.executed == [ 'xyzzy' ], t.executed + + a = SCons.Action.CommandAction(["xyzzy"]) e = Environment(SPAWN = func, SHELL = 'fake shell') a([], [], e) assert t.executed == [ 'xyzzy' ], t.executed diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py index acf0722..77ac9f4 100644 --- a/src/engine/SCons/BuilderTests.py +++ b/src/engine/SCons/BuilderTests.py @@ -864,7 +864,7 @@ class BuilderTestCase(unittest.TestCase): def func(self): pass - scanner = SCons.Scanner.Scanner(func, name='fooscan') + scanner = SCons.Scanner.Base(func, name='fooscan') b1 = SCons.Builder.Builder(action='bld', target_scanner=scanner) b2 = SCons.Builder.Builder(action='bld', target_scanner=scanner) diff --git a/src/engine/SCons/Conftest.py b/src/engine/SCons/Conftest.py index ddb1a99..81a8ee4 100644 --- a/src/engine/SCons/Conftest.py +++ b/src/engine/SCons/Conftest.py @@ -341,7 +341,6 @@ def CheckLib(context, libs, func_name = None, header = None, sure $CFLAGS, $CPPFLAGS and $LIBS are set correctly. Returns an empty string for success, an error message for failure. """ - from SCons.Debug import Trace # Include "confdefs.h" first, so that the header can use HAVE_HEADER_H. if context.headerfilename: includetext = '#include "%s"' % context.headerfilename diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py index 96c3cf8..11bace3 100644 --- a/src/engine/SCons/Defaults.py +++ b/src/engine/SCons/Defaults.py @@ -40,7 +40,6 @@ import os import os.path import shutil import stat -import string import time import types import sys diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index 6c392a5..e5eb40c 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -38,7 +38,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import copy import os import os.path -import popen2 import string from UserDict import UserDict @@ -47,6 +46,7 @@ import SCons.Builder from SCons.Debug import logInstanceCreation import SCons.Defaults import SCons.Errors +import SCons.Memoize import SCons.Node import SCons.Node.Alias import SCons.Node.FS @@ -54,7 +54,6 @@ import SCons.Node.Python import SCons.Platform import SCons.SConsign import SCons.Sig -import SCons.Sig.TimeStamp import SCons.Subst import SCons.Tool import SCons.Util @@ -508,17 +507,17 @@ class SubstitutionEnvironment: the result of that evaluation is then added to the dict. """ dict = { - 'ASFLAGS' : [], - 'CFLAGS' : [], - 'CCFLAGS' : [], + 'ASFLAGS' : SCons.Util.CLVar(''), + 'CFLAGS' : SCons.Util.CLVar(''), + 'CCFLAGS' : SCons.Util.CLVar(''), 'CPPDEFINES' : [], - 'CPPFLAGS' : [], + 'CPPFLAGS' : SCons.Util.CLVar(''), 'CPPPATH' : [], - 'FRAMEWORKPATH' : [], - 'FRAMEWORKS' : [], + 'FRAMEWORKPATH' : SCons.Util.CLVar(''), + 'FRAMEWORKS' : SCons.Util.CLVar(''), 'LIBPATH' : [], 'LIBS' : [], - 'LINKFLAGS' : [], + 'LINKFLAGS' : SCons.Util.CLVar(''), 'RPATH' : [], } @@ -620,7 +619,7 @@ class SubstitutionEnvironment: if arg[2:]: append_define(arg[2:]) else: - appencd_next_arg_to = 'CPPDEFINES' + append_next_arg_to = 'CPPDEFINES' elif arg == '-framework': append_next_arg_to = 'FRAMEWORKS' elif arg[:14] == '-frameworkdir=': @@ -665,7 +664,7 @@ class SubstitutionEnvironment: apply(self.Append, (), args) return self for key, value in args.items(): - if value == '': + if not value: continue try: orig = self[key] @@ -673,10 +672,24 @@ class SubstitutionEnvironment: orig = value else: if not orig: - orig = [] - elif not SCons.Util.is_List(orig): - orig = [orig] - orig = orig + value + orig = value + elif value: + # Add orig and value. The logic here was lifted from + # part of env.Append() (see there for a lot of comments + # about the order in which things are tried) and is + # used mainly to handle coercion of strings to CLVar to + # "do the right thing" given (e.g.) an original CCFLAGS + # string variable like '-pipe -Wall'. + try: + orig = orig + value + except (KeyError, TypeError): + try: + add_to_orig = orig.append + except AttributeError: + value.insert(0, orig) + orig = value + else: + add_to_orig(value) t = [] if key[-4:] == 'PATH': ### keep left-most occurence @@ -1314,12 +1327,15 @@ class Base(SubstitutionEnvironment): del kw[k] apply(self.Replace, (), kw) + def _find_toolpath_dir(self, tp): + return self.fs.Dir(self.subst(tp)).srcnode().abspath + def Tool(self, tool, toolpath=None, **kw): if SCons.Util.is_String(tool): tool = self.subst(tool) if toolpath is None: toolpath = self.get('toolpath', []) - toolpath = map(self.subst, toolpath) + toolpath = map(self._find_toolpath_dir, toolpath) tool = apply(SCons.Tool.Tool, (tool, toolpath), kw) tool(self) @@ -1514,6 +1530,15 @@ class Base(SubstitutionEnvironment): t.set_noclean() return tlist + def NoCache(self, *targets): + """Tags a target so that it will not be cached""" + tlist = [] + for t in targets: + tlist.extend(self.arg2nodes(t, self.fs.Entry)) + for t in tlist: + t.set_nocache() + return tlist + def Entry(self, name, *args, **kw): """ """ @@ -1575,7 +1600,10 @@ class Base(SubstitutionEnvironment): tgt = [] for dnode in dnodes: for src in sources: - target = self.fs.Entry(src.name, dnode) + # 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) tgt.extend(InstallBuilder(self, target, src)) return tgt @@ -1631,7 +1659,7 @@ class Base(SubstitutionEnvironment): arg = self.subst(arg) nargs.append(arg) nkw = self.subst_kw(kw) - return apply(SCons.Scanner.Scanner, nargs, nkw) + return apply(SCons.Scanner.Base, nargs, nkw) def SConsignFile(self, name=".sconsign", dbm_module=None): if not name is None: @@ -1746,7 +1774,7 @@ class OverrideEnvironment(Base): def __getattr__(self, name): return getattr(self.__dict__['__subject'], name) def __setattr__(self, name, value): - return setattr(self.__dict__['__subject'], name, value) + setattr(self.__dict__['__subject'], name, value) # Methods that make this class act like a dictionary. def __getitem__(self, key): diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py index c015bc1..70f9026 100644 --- a/src/engine/SCons/EnvironmentTests.py +++ b/src/engine/SCons/EnvironmentTests.py @@ -695,7 +695,7 @@ sys.exit(1) "-pthread " + \ "-mno-cygwin -mwindows " + \ "-arch i386 -isysroot /tmp +DD64 " + \ - "-DFOO -DBAR=value" + "-DFOO -DBAR=value -D BAZ" d = env.ParseFlags(s) @@ -705,7 +705,7 @@ sys.exit(1) '-pthread', '-mno-cygwin', ('-arch', 'i386'), ('-isysroot', '/tmp'), '+DD64'], d['CCFLAGS'] - assert d['CPPDEFINES'] == ['FOO', ['BAR', 'value']], d['CPPDEFINES'] + assert d['CPPDEFINES'] == ['FOO', ['BAR', 'value'], 'BAZ'], d['CPPDEFINES'] assert d['CPPFLAGS'] == ['-Wp,-cpp'], d['CPPFLAGS'] assert d['CPPPATH'] == ['/usr/include/fum', 'bar'], d['CPPPATH'] assert d['FRAMEWORKPATH'] == ['fwd1', 'fwd2', 'fwd3'], d['FRAMEWORKPATH'] @@ -725,7 +725,7 @@ sys.exit(1) """ env = SubstitutionEnvironment() env.MergeFlags('') - assert env['CCFLAGS'] == [], env['CCFLAGS'] + assert not env.has_key('CCFLAGS'), env['CCFLAGS'] env.MergeFlags('-X') assert env['CCFLAGS'] == ['-X'], env['CCFLAGS'] env.MergeFlags('-X') @@ -2805,6 +2805,9 @@ def generate(env): for tnode in tgt: assert tnode.builder == InstallBuilder + tgt = env.Install('export', 'subdir/#file') + assert str(tgt[0]) == os.path.normpath('export/#file'), str(tgt[0]) + env.File('export/foo1') exc_caught = None diff --git a/src/engine/SCons/Executor.py b/src/engine/SCons/Executor.py index 4b15010..12114bc 100644 --- a/src/engine/SCons/Executor.py +++ b/src/engine/SCons/Executor.py @@ -33,7 +33,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import string from SCons.Debug import logInstanceCreation -import SCons.Util +import SCons.Memoize class Executor: @@ -63,8 +63,10 @@ class Executor: self._memo = {} def set_action_list(self, action): + import SCons.Util if not SCons.Util.is_List(action): if not action: + import SCons.Errors raise SCons.Errors.UserError, "Executor must have an action." action = [action] self.action_list = action diff --git a/src/engine/SCons/Job.py b/src/engine/SCons/Job.py index 8dde905..9832f14 100644 --- a/src/engine/SCons/Job.py +++ b/src/engine/SCons/Job.py @@ -170,7 +170,7 @@ else: self.resultsQueue = Queue.Queue(0) # Create worker threads - for i in range(num): + for _ in range(num): Worker(self.requestQueue, self.resultsQueue) def put(self, obj): diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index 9e0e6f6..4d269d2 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -47,6 +47,7 @@ import cStringIO import SCons.Action from SCons.Debug import logInstanceCreation import SCons.Errors +import SCons.Memoize import SCons.Node import SCons.Subst import SCons.Util @@ -249,6 +250,8 @@ CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None) def CachePushFunc(target, source, env): t = target[0] + if t.nocache: + return fs = t.fs cachedir, cachefile = t.cachepath() if fs.exists(cachefile): @@ -829,7 +832,7 @@ class Entry(Base): morph this Entry.""" try: self = self.disambiguate(must_exist=1) - except SCons.Errors.UserError, e: + except SCons.Errors.UserError: # There was nothing on disk with which to disambiguate # this entry. Leave it as an Entry, but return a null # string so calls to get_contents() in emitters and the @@ -1054,7 +1057,7 @@ class FS(LocalFS): path_norm = string.split(_my_normcase(name), os.sep) first_orig = path_orig.pop(0) # strip first element - first_norm = path_norm.pop(0) # strip first element + unused = path_norm.pop(0) # strip first element drive, path_first = os.path.splitdrive(first_orig) if path_first: @@ -1477,9 +1480,11 @@ class Dir(Base): return result def get_env_scanner(self, env, kw={}): + import SCons.Defaults return SCons.Defaults.DirEntryScanner def get_target_scanner(self): + import SCons.Defaults return SCons.Defaults.DirEntryScanner def get_found_includes(self, env, scanner, path): @@ -2033,6 +2038,8 @@ class File(Base): Returns true iff the node was successfully retrieved. """ + if self.nocache: + return None b = self.is_derived() if not b and not self.has_src_builder(): return None @@ -2298,7 +2305,7 @@ class File(Base): return str(self.rfile()) def cachepath(self): - if not self.fs.CachePath: + if self.nocache or not self.fs.CachePath: return None, None ninfo = self.get_binfo().ninfo if not hasattr(ninfo, 'bsig'): diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index 405010c..fa682a2 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -52,6 +52,7 @@ import UserList from SCons.Debug import logInstanceCreation import SCons.Executor +import SCons.Memoize import SCons.SConsign import SCons.Util @@ -202,14 +203,13 @@ class Node: self.state = no_state self.precious = None self.noclean = 0 + self.nocache = 0 self.always_build = None self.found_includes = {} self.includes = None self.attributes = self.Attrs() # Generic place to stick information about the Node. self.side_effect = 0 # true iff this node is a side effect self.side_effects = [] # the side effects of building this target - self.pre_actions = [] - self.post_actions = [] self.linked = 0 # is this node linked to the build directory? self.clear_memoized_values() @@ -765,6 +765,12 @@ class Node: # output in Util.py can use it as an index. self.noclean = noclean and 1 or 0 + def set_nocache(self, nocache = 1): + """Set the Node's nocache value.""" + # Make sure nocache is an integer so the --debug=stree + # output in Util.py can use it as an index. + self.nocache = nocache and 1 or 0 + def set_always_build(self, always_build = 1): """Set the Node's always_build value.""" self.always_build = always_build diff --git a/src/engine/SCons/Options/BoolOption.py b/src/engine/SCons/Options/BoolOption.py index f38bf02..80b607d 100644 --- a/src/engine/SCons/Options/BoolOption.py +++ b/src/engine/SCons/Options/BoolOption.py @@ -36,18 +36,16 @@ Usage example: __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" -__all__ = ('BoolOption', 'True', 'False') +__all__ = ('BoolOption') import string +import SCons.compat import SCons.Errors __true_strings = ('y', 'yes', 'true', 't', '1', 'on' , 'all' ) __false_strings = ('n', 'no', 'false', 'f', '0', 'off', 'none') -# we need this since SCons should work version indepentant -True, False = 1, 0 - def _text2bool(val): """ diff --git a/src/engine/SCons/Options/BoolOptionTests.py b/src/engine/SCons/Options/BoolOptionTests.py index 845f251..07b5b79 100644 --- a/src/engine/SCons/Options/BoolOptionTests.py +++ b/src/engine/SCons/Options/BoolOptionTests.py @@ -23,6 +23,8 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +import SCons.compat + import sys import unittest @@ -91,8 +93,8 @@ class BoolOptionTestCase(unittest.TestCase): o = opts.options[0] env = { - 'T' : SCons.Options.True, - 'F' : SCons.Options.False, + 'T' : True, + 'F' : False, 'N' : 'xyzzy', } diff --git a/src/engine/SCons/Options/PackageOption.py b/src/engine/SCons/Options/PackageOption.py index 9ecb42a..3b4f0ce 100644 --- a/src/engine/SCons/Options/PackageOption.py +++ b/src/engine/SCons/Options/PackageOption.py @@ -52,15 +52,15 @@ Usage example: __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" -__all__ = ('PackageOption', 'True', 'False') +__all__ = ('PackageOption') import string -from BoolOption import True, False +import SCons.compat import SCons.Errors -__enable_strings = (str(True), 'yes', 'true', 'on', 'enable', 'search') -__disable_strings = (str(False), 'no', 'false', 'off', 'disable') +__enable_strings = ('1', 'yes', 'true', 'on', 'enable', 'search') +__disable_strings = ('0', 'no', 'false', 'off', 'disable') def _converter(val): """ @@ -78,12 +78,10 @@ def _validator(key, val, env, searchfunc): """ # todo: write validator, check for path import os - if env[key] == False: - pass - elif env[key] == True: + if env[key] is True: if searchfunc: env[key] = searchfunc(key, val) - elif not os.path.exists(val): + elif env[key] and not os.path.exists(val): raise SCons.Errors.UserError( 'Path does not exist for option %s: %s' % (key, val)) diff --git a/src/engine/SCons/Options/PackageOptionTests.py b/src/engine/SCons/Options/PackageOptionTests.py index 7c868e5..68f14e5 100644 --- a/src/engine/SCons/Options/PackageOptionTests.py +++ b/src/engine/SCons/Options/PackageOptionTests.py @@ -23,12 +23,13 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +import SCons.compat + import sys import unittest import SCons.Errors import SCons.Options -from SCons.Options.BoolOption import True, False import TestCmd @@ -96,7 +97,7 @@ class PackageOptionTestCase(unittest.TestCase): o = opts.options[0] - env = {'F':0, 'T':1, 'X':'x'} + env = {'F':False, 'T':True, 'X':'x'} exists = test.workpath('exists') does_not_exist = test.workpath('does_not_exist') diff --git a/src/engine/SCons/Options/__init__.py b/src/engine/SCons/Options/__init__.py index 5c30be6..66c2143 100644 --- a/src/engine/SCons/Options/__init__.py +++ b/src/engine/SCons/Options/__init__.py @@ -36,7 +36,7 @@ import SCons.Errors import SCons.Util import SCons.Warnings -from BoolOption import BoolOption, True, False # okay +from BoolOption import BoolOption # okay from EnumOption import EnumOption # okay from ListOption import ListOption # naja from PackageOption import PackageOption # naja @@ -227,8 +227,6 @@ class Options: of the options. """ - help_text = "" - if sort: options = self.options[:] options.sort(lambda x,y,func=sort: func(x.key,y.key)) diff --git a/src/engine/SCons/PathList.py b/src/engine/SCons/PathList.py index b757bd3..81b8135 100644 --- a/src/engine/SCons/PathList.py +++ b/src/engine/SCons/PathList.py @@ -35,6 +35,7 @@ Do the Right Thing (almost) regardless of how the variable is specified. import os import string +import SCons.Memoize import SCons.Util # diff --git a/src/engine/SCons/Platform/darwin.py b/src/engine/SCons/Platform/darwin.py index 7883795..fc4c773 100644 --- a/src/engine/SCons/Platform/darwin.py +++ b/src/engine/SCons/Platform/darwin.py @@ -33,7 +33,6 @@ selection method. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import posix -import os def generate(env): posix.generate(env) diff --git a/src/engine/SCons/Platform/win32.py b/src/engine/SCons/Platform/win32.py index 148d9df..8d35a8d 100644 --- a/src/engine/SCons/Platform/win32.py +++ b/src/engine/SCons/Platform/win32.py @@ -93,7 +93,6 @@ def piped_spawn(sh, escape, cmd, args, env, stdout, stderr): try: ret = exitvalmap[e[0]] except KeyError: - result = 127 sys.stderr.write("scons: unknown OSError exception code %d - %s: %s\n" % (e[0], cmd, e[1])) if stderr != None: stderr.write("scons: %s: %s\n" % (cmd, e[1])) diff --git a/src/engine/SCons/SConf.py b/src/engine/SCons/SConf.py index 5c20f26..21305b9 100644 --- a/src/engine/SCons/SConf.py +++ b/src/engine/SCons/SConf.py @@ -39,6 +39,7 @@ import types import SCons.Action import SCons.Builder import SCons.Errors +import SCons.Job import SCons.Node.FS import SCons.Taskmaster import SCons.Util @@ -141,7 +142,6 @@ def _createSource( target, source, env ): fd.write(source[0].get_contents()) fd.close() def _stringSource( target, source, env ): - import string return (str(target[0]) + ' <-\n |' + string.replace( source[0].get_contents(), '\n', "\n |" ) ) @@ -188,6 +188,11 @@ class Streamer: Return everything written to orig since the Streamer was created. """ return self.s.getvalue() + + def flush(self): + if self.orig: + self.orig.flush() + self.s.flush() class SConfBuildTask(SCons.Taskmaster.Task): @@ -229,7 +234,6 @@ class SConfBuildTask(SCons.Taskmaster.Task): except AttributeError: # Earlier versions of Python don't have sys.excepthook... def excepthook(type, value, tb): - import traceback traceback.print_tb(tb) print type, value apply(excepthook, self.exc_info()) @@ -597,7 +601,8 @@ class SConf: else: _ac_config_logs[self.logfile] = None log_mode = "w" - self.logstream = open(str(self.logfile), log_mode) + fp = open(str(self.logfile), log_mode) + self.logstream = SCons.Util.Unbuffered(fp) # logfile may stay in a build directory, so we tell # the build system not to override it with a eventually # existing file with the same name in the source directory diff --git a/src/engine/SCons/SConsign.py b/src/engine/SCons/SConsign.py index 67b2aff..dcd6979 100644 --- a/src/engine/SCons/SConsign.py +++ b/src/engine/SCons/SConsign.py @@ -32,8 +32,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import cPickle import os import os.path -import string -import time import SCons.dblite import SCons.Sig diff --git a/src/engine/SCons/Scanner/Dir.py b/src/engine/SCons/Scanner/Dir.py index fb23d1b..535150a 100644 --- a/src/engine/SCons/Scanner/Dir.py +++ b/src/engine/SCons/Scanner/Dir.py @@ -23,8 +23,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" -import string - import SCons.Node.FS import SCons.Scanner diff --git a/src/engine/SCons/Scanner/ScannerTests.py b/src/engine/SCons/Scanner/ScannerTests.py index bd8546f..30dc1df 100644 --- a/src/engine/SCons/Scanner/ScannerTests.py +++ b/src/engine/SCons/Scanner/ScannerTests.py @@ -82,29 +82,37 @@ class FindPathDirsTestCase(unittest.TestCase): class ScannerTestCase(unittest.TestCase): def test_creation(self): - """Test creation of Scanner objects through the Scanner() function""" + """Test creation of Scanner objects""" def func(self): pass - s = SCons.Scanner.Scanner(func) + s = SCons.Scanner.Base(func) + assert isinstance(s, SCons.Scanner.Base), s + s = SCons.Scanner.Base({}) assert isinstance(s, SCons.Scanner.Base), s - s = SCons.Scanner.Scanner({}) - assert isinstance(s, SCons.Scanner.Selector), s - s = SCons.Scanner.Scanner(func, name='fooscan') + s = SCons.Scanner.Base(func, name='fooscan') assert str(s) == 'fooscan', str(s) - s = SCons.Scanner.Scanner({}, name='barscan') + s = SCons.Scanner.Base({}, name='barscan') assert str(s) == 'barscan', str(s) - s = SCons.Scanner.Scanner(func, name='fooscan', argument=9) + s = SCons.Scanner.Base(func, name='fooscan', argument=9) assert str(s) == 'fooscan', str(s) assert s.argument == 9, s.argument - s = SCons.Scanner.Scanner({}, name='fooscan', argument=888) + s = SCons.Scanner.Base({}, name='fooscan', argument=888) assert str(s) == 'fooscan', str(s) assert s.argument == 888, s.argument class BaseTestCase(unittest.TestCase): + class skey_node: + def __init__(self, key): + self.key = key + def scanner_key(self): + return self.key + def rexists(self): + return 1 + def func(self, filename, env, target, *args): self.filename = filename self.env = env @@ -132,6 +140,29 @@ class BaseTestCase(unittest.TestCase): else: self.failIf(hasattr(self, "arg"), "an argument was given when it shouldn't have been") + def test___call__dict(self): + """Test calling Scanner.Base objects with a dictionary""" + called = [] + def s1func(node, env, path, called=called): + called.append('s1func') + called.append(node) + return [] + def s2func(node, env, path, called=called): + called.append('s2func') + called.append(node) + return [] + s1 = SCons.Scanner.Base(s1func) + s2 = SCons.Scanner.Base(s2func) + selector = SCons.Scanner.Base({'.x' : s1, '.y' : s2}) + nx = self.skey_node('.x') + env = DummyEnvironment() + selector(nx, env, []) + assert called == ['s1func', nx], called + del called[:] + ny = self.skey_node('.y') + selector(ny, env, []) + assert called == ['s2func', ny], called + def test_path(self): """Test the Scanner.Base path() method""" def pf(env, cwd, target, source, argument=None): @@ -277,6 +308,23 @@ class BaseTestCase(unittest.TestCase): s = scanner.select('.x') assert s is scanner, s + selector = SCons.Scanner.Base({'.x' : 1, '.y' : 2}) + s = selector.select(self.skey_node('.x')) + assert s == 1, s + s = selector.select(self.skey_node('.y')) + assert s == 2, s + s = selector.select(self.skey_node('.z')) + assert s is None, s + + def test_add_scanner(self): + """Test the Scanner.Base add_scanner() method""" + selector = SCons.Scanner.Base({'.x' : 1, '.y' : 2}) + s = selector.select(self.skey_node('.z')) + assert s is None, s + selector.add_scanner('.z', 3) + s = selector.select(self.skey_node('.z')) + assert s == 3, s + def test___str__(self): """Test the Scanner.Base __str__() method""" scanner = SCons.Scanner.Base(function = self.func) diff --git a/src/engine/SCons/Scanner/__init__.py b/src/engine/SCons/Scanner/__init__.py index 679efca..db93f61 100644 --- a/src/engine/SCons/Scanner/__init__.py +++ b/src/engine/SCons/Scanner/__init__.py @@ -45,9 +45,17 @@ class _Null: _null = _Null def Scanner(function, *args, **kw): - """Public interface factory function for creating different types + """ + Public interface factory function for creating different types of Scanners based on the different types of "functions" that may - be supplied.""" + be supplied. + + TODO: Deprecate this some day. We've moved the functionality + inside the Base class and really don't need this factory function + any more. It was, however, used by some of our Tool modules, so + the call probably ended up in various people's custom modules + patterned on SCons code. + """ if SCons.Util.is_Dict(function): return apply(Selector, (function,) + args, kw) else: @@ -83,7 +91,7 @@ class Base: function, name = "NONE", argument = _null, - skeys = [], + skeys = _null, path_function = None, node_class = SCons.Node.FS.Entry, node_factory = None, @@ -159,7 +167,14 @@ class Base: self.path_function = path_function self.name = name self.argument = argument + + if skeys is _null: + if SCons.Util.is_Dict(function): + skeys = function.keys() + else: + skeys = [] self.skeys = skeys + self.node_class = node_class self.node_factory = node_factory self.scan_check = scan_check @@ -188,10 +203,13 @@ class Base: if self.scan_check and not self.scan_check(node, env): return [] + self = self.select(node) + if not self.argument is _null: list = self.function(node, env, path, self.argument) else: list = self.function(node, env, path) + kw = {} if hasattr(node, 'dir'): kw['directory'] = node.dir @@ -221,12 +239,19 @@ class Base: self.skeys.append(skey) def get_skeys(self, env=None): - if SCons.Util.is_String(self.skeys): + if env and SCons.Util.is_String(self.skeys): return env.subst_list(self.skeys)[0] return self.skeys def select(self, node): - return self + if SCons.Util.is_Dict(self.function): + key = node.scanner_key() + try: + return self.function[key] + except KeyError: + return None + else: + return self def _recurse_all_nodes(self, nodes): return nodes @@ -236,15 +261,27 @@ class Base: recurse_nodes = _recurse_no_nodes + def add_scanner(self, skey, scanner): + self.function[skey] = scanner + self.add_skey(skey) + class Selector(Base): """ A class for selecting a more specific scanner based on the scanner_key() (suffix) for a specific Node. + + TODO: This functionality has been moved into the inner workings of + the Base class, and this class will be deprecated at some point. + (It was never exposed directly as part of the public interface, + although it is used by the Scanner() factory function that was + used by various Tool modules and therefore was likely a template + for custom modules that may be out there.) """ def __init__(self, dict, *args, **kw): apply(Base.__init__, (self, None,)+args, kw) self.dict = dict + self.skeys = dict.keys() def __call__(self, node, env, path = ()): return self.select(node)(node, env, path) @@ -257,6 +294,7 @@ class Selector(Base): def add_scanner(self, skey, scanner): self.dict[skey] = scanner + self.add_skey(skey) class Current(Base): diff --git a/src/engine/SCons/Script/Main.py b/src/engine/SCons/Script/Main.py index 96f1526..6a0ad81 100644 --- a/src/engine/SCons/Script/Main.py +++ b/src/engine/SCons/Script/Main.py @@ -36,6 +36,8 @@ it goes here. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +import SCons.compat + import os import os.path import random @@ -62,6 +64,7 @@ import SCons.Node import SCons.Node.FS from SCons.Optik import OptionParser, SUPPRESS_HELP, OptionValueError import SCons.SConf +import SCons.Script import SCons.Sig import SCons.Taskmaster import SCons.Util @@ -180,15 +183,8 @@ class BuildTask(SCons.Taskmaster.Task): def postprocess(self): if self.top: t = self.targets[0] - if print_tree: - print - SCons.Util.print_tree(t, get_all_children) - if print_stree: - print - SCons.Util.print_tree(t, get_all_children, showtags=2) - if print_dtree: - print - SCons.Util.print_tree(t, get_derived_children) + for tp in tree_printers: + tp.display(t) if print_includes: tree = t.render_include_tree() if tree: @@ -291,18 +287,37 @@ class QuestionTask(SCons.Taskmaster.Task): def executed(self): pass + +class TreePrinter: + def __init__(self, derived=False, prune=False, status=False): + self.derived = derived + self.prune = prune + self.status = status + def get_all_children(self, node): + return node.all_children() + def get_derived_children(self, node): + children = node.all_children(None) + return filter(lambda x: x.has_builder(), children) + def display(self, t): + if self.derived: + func = self.get_derived_children + else: + func = self.get_all_children + s = self.status and 2 or 0 + SCons.Util.print_tree(t, func, prune=self.prune, showtags=s) + + # Global variables +tree_printers = [] + keep_going_on_error = 0 -print_dtree = 0 print_explanations = 0 print_includes = 0 print_objects = 0 print_memoizer = 0 print_stacktrace = 0 -print_stree = 0 print_time = 0 -print_tree = 0 ignore_errors = 0 sconscript_time = 0 command_time = 0 @@ -390,12 +405,6 @@ memory_stats = MemStats() # utility functions -def get_all_children(node): return node.all_children() - -def get_derived_children(node): - children = node.all_children(None) - return filter(lambda x: x.has_builder(), children) - def _scons_syntax_error(e): """Handle syntax errors. Print out a message and show where the error occurred. @@ -538,10 +547,10 @@ def _SConstruct_exists(dirname=''): def _set_globals(options): global keep_going_on_error, ignore_errors - global count_stats, print_dtree + global count_stats global print_explanations, print_includes, print_memoizer - global print_objects, print_stacktrace, print_stree - global print_time, print_tree + global print_objects, print_stacktrace, print_time + global tree_printers global memory_stats keep_going_on_error = options.keep_going @@ -555,7 +564,7 @@ def _set_globals(options): if "count" in debug_values: count_stats.enable(sys.stdout) if "dtree" in debug_values: - print_dtree = 1 + tree_printers.append(TreePrinter(derived=True)) if "explain" in debug_values: print_explanations = 1 if "findlibs" in debug_values: @@ -573,11 +582,11 @@ def _set_globals(options): if "stacktrace" in debug_values: print_stacktrace = 1 if "stree" in debug_values: - print_stree = 1 + tree_printers.append(TreePrinter(status=True)) if "time" in debug_values: print_time = 1 if "tree" in debug_values: - print_tree = 1 + tree_printers.append(TreePrinter()) ignore_errors = options.ignore_errors def _create_path(plist): @@ -589,6 +598,47 @@ def _create_path(plist): path = path + '/' + d return path +def _load_site_scons_dir(topdir, site_dir_name=None): + """Load the site_scons dir under topdir. + Adds site_scons to sys.path, imports site_scons/site_init.py, + and adds site_scons/site_tools to default toolpath.""" + if site_dir_name: + err_if_not_found = True # user specified: err if missing + else: + site_dir_name = "site_scons" + err_if_not_found = False + + site_dir = os.path.join(topdir.path, site_dir_name) + if not os.path.exists(site_dir): + if err_if_not_found: + raise SCons.Errors.UserError, "site dir %s not found."%site_dir + return + + site_init_filename = "site_init.py" + site_init_modname = "site_init" + site_tools_dirname = "site_tools" + sys.path = [site_dir] + sys.path + site_init_file = os.path.join(site_dir, site_init_filename) + site_tools_dir = os.path.join(site_dir, site_tools_dirname) + if os.path.exists(site_init_file): + import imp + try: + fp, pathname, description = imp.find_module(site_init_modname, + [site_dir]) + try: + imp.load_module(site_init_modname, fp, pathname, description) + finally: + if fp: + fp.close() + except ImportError, e: + sys.stderr.write("Can't import site init file '%s': %s\n"%(site_init_file, e)) + raise + except Exception, e: + sys.stderr.write("Site init file '%s' raised exception: %s\n"%(site_init_file, e)) + raise + if os.path.exists(site_tools_dir): + SCons.Tool.DefaultToolpath.append(os.path.abspath(site_tools_dir)) + def version_string(label, module): fmt = "\t%s: v%s.%s, %s, by %s on %s\n" return fmt % (label, @@ -676,7 +726,9 @@ class OptParser(OptionParser): "pdb", "presub", "stacktrace", "stree", "time", "tree"] - deprecated_debug_options = [ "nomemoizer", ] + 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: @@ -686,8 +738,9 @@ class OptParser(OptionParser): except AttributeError: parser.values.debug = [] parser.values.debug.append(value) - elif value in deprecated_debug_options: - w = "The --debug=%s option is deprecated and has no effect." % 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) @@ -773,6 +826,10 @@ class OptParser(OptionParser): '--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.") @@ -790,10 +847,36 @@ class OptParser(OptionParser): 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, " @@ -811,7 +894,8 @@ class OptParser(OptionParser): metavar="WARNING-SPEC", help="Enable or disable warnings.") - self.add_option('-Y', '--repository', nargs=1, action="append", + 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", @@ -1076,6 +1160,11 @@ def _main(args, parser): if options.cache_show: fs.cache_show = 1 + if options.site_dir: + _load_site_scons_dir(d, options.site_dir) + elif not options.no_site_dir: + _load_site_scons_dir(d) + if options.include_dir: sys.path = options.include_dir + sys.path @@ -1092,16 +1181,7 @@ def _main(args, parser): SCons.Script._Add_Targets(targets) SCons.Script._Add_Arguments(xmit_args) - class Unbuffered: - def __init__(self, file): - self.file = file - def write(self, arg): - self.file.write(arg) - self.file.flush() - def __getattr__(self, attr): - return getattr(self.file, attr) - - sys.stdout = Unbuffered(sys.stdout) + sys.stdout = SCons.Util.Unbuffered(sys.stdout) memory_stats.append('before reading SConscript files:') count_stats.append(('pre-', 'read')) diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py index 749be6d..d25e44c 100644 --- a/src/engine/SCons/Script/SConscript.py +++ b/src/engine/SCons/Script/SConscript.py @@ -301,12 +301,12 @@ def SConscript_exception(file=sys.stderr): def annotate(node): """Annotate a node with the stack frame describing the SConscript file and line number that created it.""" - tb = exc_tb = sys.exc_info()[2] + tb = sys.exc_info()[2] while tb and not tb.tb_frame.f_locals.has_key(stack_bottom): tb = tb.tb_next if not tb: # We did not find any exec of an SConscript file: what?! - raise InternalError, "could not find SConscript stack frame" + raise SCons.Errors.InternalError, "could not find SConscript stack frame" node.creator = traceback.extract_stack(tb)[0] # The following line would cause each Node to be annotated using the diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py index 067f540..35bbbf7 100644 --- a/src/engine/SCons/Script/__init__.py +++ b/src/engine/SCons/Script/__init__.py @@ -109,12 +109,12 @@ OptParser = Main.OptParser SConscriptSettableOptions = Main.SConscriptSettableOptions keep_going_on_error = Main.keep_going_on_error -print_dtree = Main.print_dtree +#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_tree = Main.print_tree +#print_tree = Main.print_tree memory_stats = Main.memory_stats ignore_errors = Main.ignore_errors #sconscript_time = Main.sconscript_time @@ -289,6 +289,7 @@ GlobalDefaultEnvironmentFunctions = [ 'Depends', 'Dir', 'NoClean', + 'NoCache', 'Entry', 'Execute', 'File', @@ -341,6 +342,7 @@ GlobalDefaultBuilders = [ for name in GlobalDefaultEnvironmentFunctions + GlobalDefaultBuilders: exec "%s = _SConscript.DefaultEnvironmentCall(%s)" % (name, repr(name)) +del name # There are a handful of variables that used to live in the # Script/SConscript.py module that some SConscript files out there were @@ -352,6 +354,10 @@ for name in GlobalDefaultEnvironmentFunctions + GlobalDefaultBuilders: # this way by hanging some attributes off the "SConscript" object here. SConscript = _SConscript.DefaultEnvironmentCall('SConscript') +# Make SConscript look enough like the module it used to be so +# that pychecker doesn't barf. +SConscript.__name__ = 'SConscript' + SConscript.Arguments = ARGUMENTS SConscript.ArgList = ARGLIST SConscript.BuildTargets = BUILD_TARGETS diff --git a/src/engine/SCons/Subst.py b/src/engine/SCons/Subst.py index 2a993be..989f1dd 100644 --- a/src/engine/SCons/Subst.py +++ b/src/engine/SCons/Subst.py @@ -442,7 +442,9 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={ # This probably indicates that it's a callable # object that doesn't match our calling arguments # (like an Action). - s = str(s) + if self.mode == SUBST_RAW: + return s + s = self.conv(s) return self.substitute(s, lvars) elif s is None: return '' @@ -646,7 +648,10 @@ def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gv # This probably indicates that it's a callable # object that doesn't match our calling arguments # (like an Action). - s = str(s) + if self.mode == SUBST_RAW: + self.append(s) + return + s = self.conv(s) self.substitute(s, lvars, within_list) elif s is None: self.this_word() diff --git a/src/engine/SCons/SubstTests.py b/src/engine/SCons/SubstTests.py index e8419f1..7ba2477 100644 --- a/src/engine/SCons/SubstTests.py +++ b/src/engine/SCons/SubstTests.py @@ -145,6 +145,8 @@ class SubstTestCase(unittest.TestCase): MyNode("/bar/ack.cpp"), MyNode("../foo/ack.c") ] + callable_object = TestCallable('callable-1') + loc = { 'xxx' : None, 'null' : '', @@ -203,7 +205,7 @@ class SubstTestCase(unittest.TestCase): 'SSS' : '$RRR', # Test callables that don't match the calling arguments. - 'CALLABLE' : TestCallable('callable-1'), + 'CALLABLE' : callable_object, } env = DummyEnv(loc) @@ -514,6 +516,16 @@ class SubstTestCase(unittest.TestCase): else: raise AssertionError, "did not catch expected UserError" + # Test that the combination of SUBST_RAW plus a pass-through + # conversion routine allows us to fetch a function through the + # dictionary. CommandAction uses this to allow delayed evaluation + # of $SPAWN variables. + x = lambda x: x + r = scons_subst("$CALLABLE", env, mode=SUBST_RAW, conv=x, gvars=gvars) + assert r is callable_object, repr(r) + r = scons_subst("$CALLABLE", env, mode=SUBST_RAW, gvars=gvars) + assert r == 'callable-1', repr(r) + # Test how we handle overriding the internal conversion routines. def s(obj): return obj @@ -594,6 +606,8 @@ class SubstTestCase(unittest.TestCase): MyNode("/bar/ack.cpp"), MyNode("../foo/ack.c") ] + callable_object = TestCallable('callable-2') + def _defines(defs): l = [] for d in defs: @@ -653,7 +667,7 @@ class SubstTestCase(unittest.TestCase): 'SSS' : '$RRR', # Test callable objects that don't match our calling arguments. - 'CALLABLE' : TestCallable('callable-2'), + 'CALLABLE' : callable_object, '_defines' : _defines, 'DEFS' : [ ('Q1', '"q1"'), ('Q2', '"$AAA"') ], @@ -792,8 +806,6 @@ class SubstTestCase(unittest.TestCase): # Test callables that don't match our calling arguments. '$CALLABLE', [['callable-2']], - # Test - # Test handling of quotes. # XXX Find a way to handle this in the future. #'aaa "bbb ccc" ddd', [['aaa', 'bbb ccc', 'ddd']], @@ -991,6 +1003,15 @@ class SubstTestCase(unittest.TestCase): else: raise AssertionError, "did not catch expected SyntaxError" + # Test that the combination of SUBST_RAW plus a pass-through + # conversion routine allows us to fetch a function through the + # dictionary. + x = lambda x: x + r = scons_subst_list("$CALLABLE", env, mode=SUBST_RAW, conv=x, gvars=gvars) + assert r == [[callable_object]], repr(r) + r = scons_subst_list("$CALLABLE", env, mode=SUBST_RAW, gvars=gvars) + assert r == [['callable-2']], repr(r) + # Test we handle overriding the internal conversion routines. def s(obj): return obj diff --git a/src/engine/SCons/Taskmaster.py b/src/engine/SCons/Taskmaster.py index 04ed19a..7439168 100644 --- a/src/engine/SCons/Taskmaster.py +++ b/src/engine/SCons/Taskmaster.py @@ -41,7 +41,8 @@ interface and the SCons build engine. There are two key classes here: which has Task subclasses that handle its specific behavior, like printing "`foo' is up to date" when a top-level target doesn't need to be built, and handling the -c option by removing - targets as its "build" action. + targets as its "build" action. There is also a separate subclass + for suppressing this output when the -q option is used. The Taskmaster instantiates a Task object for each (set of) target(s) that it decides need to be evaluated and/or built. @@ -58,6 +59,8 @@ import SCons.Errors StateString = SCons.Node.StateString + + # A subsystem for recording stats about how different Nodes are handled by # the main Taskmaster loop. There's no external control here (no need for # a --debug= option); enable it by changing the value of CollectStats. @@ -68,7 +71,7 @@ class Stats: """ A simple class for holding statistics about the disposition of a Node by the Taskmaster. If we're collecting statistics, each Node - processed by the Taskmaster gets one of these attached, in which + processed by the Taskmaster gets one of these attached, in which case the Taskmaster records its decision each time it processes the Node. (Ideally, that's just once per Node.) """ @@ -100,8 +103,11 @@ def dump_stats(): for n in StatsNodes: print (fmt % n.stats.__dict__) + str(n) + + class Task: - """Default SCons build engine task. + """ + Default SCons build engine task. This controls the interaction of the actual building of node and the rest of the engine. @@ -116,7 +122,8 @@ class Task: Note that it's generally a good idea for sub-classes to call these methods explicitly to update state, etc., rather than - roll their own interaction with Taskmaster from scratch.""" + roll their own interaction with Taskmaster from scratch. + """ def __init__(self, tm, targets, top, node): self.tm = tm self.targets = targets @@ -125,15 +132,26 @@ class Task: self.exc_clear() def display(self, message): - """Allow the calling interface to display a message + """ + Hook to allow the calling interface to display a message. + + This hook gets called as part of preparing a task for execution + (that is, a Node to be built). As part of figuring out what Node + should be built next, the actually target list may be altered, + along with a message describing the alteration. The calling + interface can subclass Task and provide a concrete implementation + of this method to see those messages. """ pass def prepare(self): - """Called just before the task is executed. + """ + Called just before the task is executed. - This unlinks all targets and makes all directories before - building anything.""" + This is mainly intended to give the target Nodes a chance to + unlink underlying files and make all necessary directories before + the Action is actually called to build the targets. + """ # Now that it's the appropriate time, give the TaskMaster a # chance to raise any exceptions it encountered while preparing @@ -155,11 +173,13 @@ class Task: return self.node def execute(self): - """Called to execute the task. + """ + Called to execute the task. This method is called from multiple threads in a parallel build, so only do thread safe stuff here. Do thread unsafe stuff in - prepare(), executed() or failed().""" + prepare(), executed() or failed(). + """ try: everything_was_cached = 1 @@ -183,13 +203,13 @@ class Task: sys.exc_info()) def executed(self): - """Called when the task has been successfully executed. + """ + Called when the task has been successfully executed. - This may have been a do-nothing operation (to preserve - build order), so check the node's state before updating - things. Most importantly, this calls back to the - Taskmaster to put any node tasks waiting on this one - back on the pending list.""" + This may have been a do-nothing operation (to preserve build + order), so we have to check the node's state before deciding + whether it was "built" or just "visited." + """ for t in self.targets: if t.get_state() == SCons.Node.executing: t.set_state(SCons.Node.executed) @@ -197,17 +217,18 @@ class Task: else: t.visited() - self.tm.executed(self.node) - def failed(self): - """Default action when a task fails: stop the build.""" + """ + Default action when a task fails: stop the build. + """ self.fail_stop() def fail_stop(self): - """Explicit stop-the-build failure.""" + """ + Explicit stop-the-build failure. + """ for t in self.targets: t.set_state(SCons.Node.failed) - self.tm.failed(self.node) self.tm.stop() # We're stopping because of a build failure, but give the @@ -217,7 +238,8 @@ class Task: self.top = 1 def fail_continue(self): - """Explicit continue-the-build failure. + """ + Explicit continue-the-build failure. This sets failure status on the target nodes and all of their dependent parent nodes. @@ -228,10 +250,9 @@ class Task: def set_state(node): node.set_state(SCons.Node.failed) t.call_for_all_waiting_parents(set_state) - self.tm.executed(self.node) - def make_ready_all(self): - """Mark all targets in a task ready for execution. + """ + Marks all targets in a task ready for execution. This is used when the interface needs every target Node to be visited--the canonical example being the "scons -c" option. @@ -243,7 +264,8 @@ class Task: s.set_state(SCons.Node.executing) def make_ready_current(self): - """Mark all targets in a task ready for execution if any target + """ + Marks all targets in a task ready for execution if any target is not current. This is the default behavior for building only what's necessary. @@ -261,11 +283,28 @@ class Task: make_ready = make_ready_current def postprocess(self): - """Post process a task after it's been executed.""" + """ + Post-processes a task after it's been executed. + + This examines all the targets just built (or not, we don't care + if the build was successful, or even if there was no build + because everything was up-to-date) to see if they have any + waiting parent Nodes, or Nodes waiting on a common side effect, + that can be put back on the candidates list. + """ + + # We may have built multiple targets, some of which may have + # common parents waiting for this build. Count up how many + # targets each parent was waiting for so we can subtract the + # values later, and so we *don't* put waiting side-effect Nodes + # back on the candidates list if the Node is also a waiting + # parent. + parents = {} for t in self.targets: for p in t.waiting_parents.keys(): parents[p] = parents.get(p, 0) + 1 + for t in self.targets: for s in t.side_effects: if s.get_state() == SCons.Node.executing: @@ -276,21 +315,47 @@ class Task: for p in s.waiting_s_e.keys(): if p.ref_count == 0: self.tm.candidates.append(p) + for p, subtract in parents.items(): p.ref_count = p.ref_count - subtract if p.ref_count == 0: self.tm.candidates.append(p) + for t in self.targets: t.postprocess() + # Exception handling subsystem. + # + # Exceptions that occur while walking the DAG or examining Nodes + # must be raised, but must be raised at an appropriate time and in + # a controlled manner so we can, if necessary, recover gracefully, + # possibly write out signature information for Nodes we've updated, + # etc. This is done by having the Taskmaster tell us about the + # exception, and letting + def exc_info(self): + """ + Returns info about a recorded exception. + """ return self.exception def exc_clear(self): + """ + Clears any recorded exception. + + This also changes the "exception_raise" attribute to point + to the appropriate do-nothing method. + """ self.exception = (None, None, None) self.exception_raise = self._no_exception_to_raise def exception_set(self, exception=None): + """ + Records an exception to be raised at the appropriate time. + + This also changes the "exception_raise" attribute to point + to the method that will, in fact + """ if not exception: exception = sys.exc_info() self.exception = exception @@ -300,14 +365,17 @@ class Task: pass def _exception_raise(self): - """Raise a pending exception that was recorded while - getting a Task ready for execution.""" - self.tm.exception_raise(self.exc_info()) - - -def order(dependencies): - """Re-order a list of dependencies (if we need to).""" - return dependencies + """ + Raises a pending exception that was recorded while getting a + Task ready for execution. + """ + exc = self.exc_info()[:] + try: + exc_type, exc_value, exc_traceback = exc + except ValueError: + exc_type, exc_value = exc + exc_traceback = None + raise exc_type, exc_value, exc_traceback def find_cycle(stack): @@ -322,24 +390,41 @@ def find_cycle(stack): class Taskmaster: - """A generic Taskmaster for handling a bunch of targets. - - Classes that override methods of this class should call - the base class method, so this class can do its thing. + """ + The Taskmaster for walking the dependency DAG. """ - def __init__(self, targets=[], tasker=Task, order=order, trace=None): + def __init__(self, targets=[], tasker=Task, order=None, trace=None): self.top_targets = targets[:] self.top_targets.reverse() self.candidates = [] self.tasker = tasker - self.ready = None # the next task that is ready to be executed + if not order: + order = lambda l: l self.order = order self.message = None self.trace = trace self.next_candidate = self.find_next_candidate def find_next_candidate(self): + """ + Returns the next candidate Node for (potential) evaluation. + + The candidate list (really a stack) initially consists of all of + the top-level (command line) targets provided when the Taskmaster + was initialized. While we walk the DAG, visiting Nodes, all the + children that haven't finished processing get pushed on to the + candidate list. Each child can then be popped and examined in + turn for whether *their* children are all up-to-date, in which + case a Task will be created for their actual evaluation and + potential building. + + Here is where we also allow candidate Nodes to alter the list of + Nodes that should be examined. This is used, for example, when + invoking SCons in a source directory. A source directory Node can + return its corresponding build directory Node, essentially saying, + "Hey, you really need to build this thing over here instead." + """ try: return self.candidates.pop() except IndexError: @@ -358,13 +443,32 @@ class Taskmaster: return node def no_next_candidate(self): + """ + Stops Taskmaster processing by not returning a next candidate. + """ return None def _find_next_ready_node(self): - """Find the next node that is ready to be built""" - - if self.ready: - return + """ + Finds the next node that is ready to be built. + + This is *the* main guts of the DAG walk. We loop through the + list of candidates, looking for something that has no un-built + children (i.e., that is a leaf Node or has dependencies that are + all leaf Nodes or up-to-date). Candidate Nodes are re-scanned + (both the target Node itself and its sources, which are always + scanned in the context of a given target) to discover implicit + dependencies. A Node that must wait for some children to be + built will be put back on the candidates list after the children + have finished building. A Node that has been put back on the + candidates list in this way may have itself (or its sources) + re-scanned, in order to handle generated header files (e.g.) and + the implicit dependencies therein. + + Note that this method does not do any signature calculation or + up-to-date check itself. All of that is handled by the Task + class. This is purely concerned with the dependency graph walk. + """ self.ready_exc = None @@ -373,8 +477,7 @@ class Taskmaster: while 1: node = self.next_candidate() if node is None: - self.ready = None - break + return None node = node.disambiguate() state = node.get_state() @@ -405,9 +508,8 @@ class Taskmaster: exc_value = sys.exc_info()[1] e = SCons.Errors.ExplicitExit(node, exc_value.code) self.ready_exc = (SCons.Errors.ExplicitExit, e) - self.ready = node if T: T.write(' SystemExit\n') - break + return node except KeyboardInterrupt: if T: T.write(' KeyboardInterrupt\n') raise @@ -417,10 +519,9 @@ class Taskmaster: # BuildDir, or a Scanner threw something). Arrange to # raise the exception when the Task is "executed." self.ready_exc = sys.exc_info() - self.ready = node if S: S.problem = S.problem + 1 if T: T.write(' exception\n') - break + return node if T and children: c = map(str, children) @@ -516,7 +617,7 @@ class Taskmaster: continue # Skip this node if it has side-effects that are currently being - # built themselves or waiting for something else being built. + # built themselves or waiting for something else being built. side_effects = filter(lambda N: N.get_state() == SCons.Node.executing, node.side_effects) @@ -531,17 +632,20 @@ class Taskmaster: # The default when we've gotten through all of the checks above: # this node is ready to be built. - self.ready = node if S: S.build = S.build + 1 if T: T.write(' evaluating %s\n' % node) - break + return node - def next_task(self): - """Return the next task to be executed.""" + return None - self._find_next_ready_node() + def next_task(self): + """ + Returns the next task to be executed. - node = self.ready + This simply asks for the next Node to be evaluated, and then wraps + it in the specific Task subclass with which we were initialized. + """ + node = self._find_next_ready_node() if node is None: return None @@ -563,27 +667,12 @@ class Taskmaster: if self.ready_exc: task.exception_set(self.ready_exc) - self.ready = None self.ready_exc = None return task def stop(self): - """Stop the current build completely.""" + """ + Stops the current build completely. + """ self.next_candidate = self.no_next_candidate - self.ready = None - - def failed(self, node): - pass - - def executed(self, node): - pass - - def exception_raise(self, exception): - exc = exception[:] - try: - exc_type, exc_value, exc_traceback = exc - except ValueError: - exc_type, exc_value = exc - exc_traceback = None - raise exc_type, exc_value, exc_traceback diff --git a/src/engine/SCons/TaskmasterTests.py b/src/engine/SCons/TaskmasterTests.py index 1803eee..f74cf34 100644 --- a/src/engine/SCons/TaskmasterTests.py +++ b/src/engine/SCons/TaskmasterTests.py @@ -681,16 +681,6 @@ class TaskmasterTestCase(unittest.TestCase): assert built_text == "MyTM.stop()" assert tm.next_task() is None - def test_failed(self): - """Test when a task has failed - """ - n1 = Node("n1") - tm = SCons.Taskmaster.Taskmaster([n1]) - t = tm.next_task() - assert t.targets == [n1], map(str, t.targets) - tm.failed(n1) - assert t.targets == [n1], map(str, t.targets) - def test_executed(self): """Test when a task has been executed """ @@ -974,20 +964,6 @@ class TaskmasterTestCase(unittest.TestCase): else: assert 0, "did not catch expected exception" - t.exception_set(("exception 4", "XYZZY")) - def fw_exc(exc): - raise 'exception_forwarded', exc - tm.exception_raise = fw_exc - try: - t.exception_raise() - except: - exc_type, exc_value = sys.exc_info()[:2] - assert exc_type == 'exception_forwarded', exc_type - assert exc_value[0] == "exception 4", exc_value[0] - assert exc_value[1] == "XYZZY", exc_value[1] - else: - assert 0, "did not catch expected exception" - def test_postprocess(self): """Test postprocessing targets to give them a chance to clean up """ diff --git a/src/engine/SCons/Tool/BitKeeper.py b/src/engine/SCons/Tool/BitKeeper.py index 2c2bfdd..b6561f4 100644 --- a/src/engine/SCons/Tool/BitKeeper.py +++ b/src/engine/SCons/Tool/BitKeeper.py @@ -34,8 +34,6 @@ selection method. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" -import os.path - import SCons.Action import SCons.Builder import SCons.Util diff --git a/src/engine/SCons/Tool/JavaCommon.py b/src/engine/SCons/Tool/JavaCommon.py index 77363f0..09ce1d1 100644 --- a/src/engine/SCons/Tool/JavaCommon.py +++ b/src/engine/SCons/Tool/JavaCommon.py @@ -148,8 +148,10 @@ if java_parsing: self.outer_state = outer_state self.tokens_to_find = 2 def parseToken(self, token): - # This is an anonymous class if and only if the next token - # is a bracket + # This is an anonymous class if and only if the next + # non-whitespace token is a bracket + if token == '\n': + return self if token == '{': self.outer_state.addAnonClass() elif token in ['"', "'"]: diff --git a/src/engine/SCons/Tool/JavaCommonTests.py b/src/engine/SCons/Tool/JavaCommonTests.py index 6d9fc43..4a7f9cf 100644 --- a/src/engine/SCons/Tool/JavaCommonTests.py +++ b/src/engine/SCons/Tool/JavaCommonTests.py @@ -265,6 +265,27 @@ public enum a {} assert classes == ['a'], classes + def test_anon_classes(self): + """Test anonymous classes""" + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java("""\ +public abstract class TestClass +{ + public void completed() + { + new Thread() + { + }.start(); + + new Thread() + { + }.start(); + } +} +""") + assert pkg_dir == None, pkg_dir + assert classes == ['TestClass$1', 'TestClass$2', 'TestClass'], classes + + if __name__ == "__main__": suite = unittest.TestSuite() diff --git a/src/engine/SCons/Tool/ToolTests.py b/src/engine/SCons/Tool/ToolTests.py index 5ddac19..52c032f 100644 --- a/src/engine/SCons/Tool/ToolTests.py +++ b/src/engine/SCons/Tool/ToolTests.py @@ -45,6 +45,8 @@ class ToolTestCase(unittest.TestCase): return self.dict[key] def __setitem__(self, key, val): self.dict[key] = val + def has_key(self, key): + return self.dict.has_key(key) env = Environment() env['BUILDERS'] = {} env['ENV'] = {} diff --git a/src/engine/SCons/Tool/__init__.py b/src/engine/SCons/Tool/__init__.py index d36478d..b2e2eff 100644 --- a/src/engine/SCons/Tool/__init__.py +++ b/src/engine/SCons/Tool/__init__.py @@ -41,6 +41,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import imp import sys +import SCons.Builder import SCons.Errors import SCons.Scanner import SCons.Scanner.C @@ -48,11 +49,13 @@ import SCons.Scanner.D import SCons.Scanner.LaTeX import SCons.Scanner.Prog +DefaultToolpath=[] + CScanner = SCons.Scanner.C.CScanner() DScanner = SCons.Scanner.D.DScanner() LaTeXScanner = SCons.Scanner.LaTeX.LaTeXScanner() ProgramScanner = SCons.Scanner.Prog.ProgramScanner() -SourceFileScanner = SCons.Scanner.Scanner({}, name='SourceFileScanner') +SourceFileScanner = SCons.Scanner.Base({}, name='SourceFileScanner') CSuffixes = [".c", ".C", ".cxx", ".cpp", ".c++", ".cc", ".h", ".H", ".hxx", ".hpp", ".hh", @@ -78,7 +81,7 @@ for suffix in LaTeXSuffixes: class Tool: def __init__(self, name, toolpath=[], **kw): self.name = name - self.toolpath = toolpath + self.toolpath = toolpath + DefaultToolpath # remember these so we can merge them into the call self.init_kw = kw diff --git a/src/engine/SCons/Tool/c++.py b/src/engine/SCons/Tool/c++.py index a44fa6d..d9370b0 100644 --- a/src/engine/SCons/Tool/c++.py +++ b/src/engine/SCons/Tool/c++.py @@ -60,6 +60,8 @@ def generate(env): Add Builders and construction variables for Visual Age C++ compilers to an Environment. """ + import SCons.Tool + import SCons.Tool.cc static_obj, shared_obj = SCons.Tool.createObjBuilders(env) for suffix in CXXSuffixes: @@ -67,6 +69,8 @@ def generate(env): shared_obj.add_action(suffix, SCons.Defaults.ShCXXAction) static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter) shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter) + + SCons.Tool.cc.add_common_cc_variables(env) env['CXX'] = 'c++' env['CXXFLAGS'] = SCons.Util.CLVar('$CCFLAGS') diff --git a/src/engine/SCons/Tool/cc.py b/src/engine/SCons/Tool/cc.py index 62b945f..d1a287a 100644 --- a/src/engine/SCons/Tool/cc.py +++ b/src/engine/SCons/Tool/cc.py @@ -40,6 +40,28 @@ CSuffixes = ['.c', '.m'] if not SCons.Util.case_sensitive_suffixes('.c', '.C'): CSuffixes.append('.C') +def add_common_cc_variables(env): + """ + Add underlying common "C compiler" variables that + are used by multiple tools (specifically, c++). + """ + if not env.has_key('_CCCOMCOM'): + env['_CCCOMCOM'] = '$CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS' + # It's a hack to test for darwin here, but the alternative + # of creating an applecc.py to contain this seems overkill. + # Maybe someday the Apple platform will require more setup and + # this logic will be moved. + env['FRAMEWORKS'] = SCons.Util.CLVar('') + env['FRAMEWORKPATH'] = SCons.Util.CLVar('') + if env['PLATFORM'] == 'darwin': + env['_CCCOMCOM'] = env['_CCCOMCOM'] + ' $_FRAMEWORKPATH' + + if not env.has_key('CCFLAGS'): + env['CCFLAGS'] = SCons.Util.CLVar('') + + if not env.has_key('SHCCFLAGS'): + env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS') + def generate(env): """ Add Builders and construction variables for C compilers to an Environment. @@ -51,22 +73,13 @@ def generate(env): shared_obj.add_action(suffix, SCons.Defaults.ShCAction) static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter) shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter) - - env['_CCCOMCOM'] = '$CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS' - # It's a hack to test for darwin here, but the alternative of creating - # an applecc.py to contain this seems overkill. Maybe someday the Apple - # platform will require more setup and this logic will be moved. - env['FRAMEWORKS'] = SCons.Util.CLVar('') - env['FRAMEWORKPATH'] = SCons.Util.CLVar('') - if env['PLATFORM'] == 'darwin': - env['_CCCOMCOM'] = env['_CCCOMCOM'] + ' $_FRAMEWORKPATH' + + add_common_cc_variables(env) env['CC'] = 'cc' - env['CCFLAGS'] = SCons.Util.CLVar('') env['CFLAGS'] = SCons.Util.CLVar('') env['CCCOM'] = '$CC -o $TARGET -c $CFLAGS $CCFLAGS $_CCCOMCOM $SOURCES' env['SHCC'] = '$CC' - env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS') env['SHCFLAGS'] = SCons.Util.CLVar('$CFLAGS') env['SHCCCOM'] = '$SHCC -o $TARGET -c $SHCFLAGS $SHCCFLAGS $_CCCOMCOM $SOURCES' diff --git a/src/engine/SCons/Tool/cvf.py b/src/engine/SCons/Tool/cvf.py index 28a1915..4bca52b 100644 --- a/src/engine/SCons/Tool/cvf.py +++ b/src/engine/SCons/Tool/cvf.py @@ -29,7 +29,6 @@ Tool-specific initialization for the Compaq Visual Fortran compiler. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" -import SCons.Util import fortran compilers = ['f90'] diff --git a/src/engine/SCons/Tool/dmd.py b/src/engine/SCons/Tool/dmd.py index cae264b..64ffb68 100644 --- a/src/engine/SCons/Tool/dmd.py +++ b/src/engine/SCons/Tool/dmd.py @@ -201,6 +201,8 @@ def generate(env): env.Append(LIBS = ['phobos']) if 'pthread' not in libs: env.Append(LIBS = ['pthread']) + if 'm' not in libs: + env.Append(LIBS = ['m']) return defaultLinker env['SMART_LINKCOM'] = smart_link[linkcom] = _smartLink diff --git a/src/engine/SCons/Tool/dvi.py b/src/engine/SCons/Tool/dvi.py index fce9850..78fb4c2 100644 --- a/src/engine/SCons/Tool/dvi.py +++ b/src/engine/SCons/Tool/dvi.py @@ -36,7 +36,7 @@ DVIBuilder = None def generate(env): try: - bld = env['BUILDERS']['DVI'] + env['BUILDERS']['DVI'] except KeyError: global DVIBuilder diff --git a/src/engine/SCons/Tool/f77.py b/src/engine/SCons/Tool/f77.py index 75c3c2d..bd1e870 100644 --- a/src/engine/SCons/Tool/f77.py +++ b/src/engine/SCons/Tool/f77.py @@ -54,6 +54,7 @@ F77Scan = SCons.Scanner.Fortran.FortranScan("F77PATH") for suffix in F77Suffixes + F77PPSuffixes: SCons.Tool.SourceFileScanner.add_scanner(suffix, F77Scan) +del suffix # fVLG = fortran.VariableListGenerator diff --git a/src/engine/SCons/Tool/f90.py b/src/engine/SCons/Tool/f90.py index cb450b6..fab4ccb 100644 --- a/src/engine/SCons/Tool/f90.py +++ b/src/engine/SCons/Tool/f90.py @@ -54,6 +54,7 @@ F90Scan = SCons.Scanner.Fortran.FortranScan("F90PATH") for suffix in F90Suffixes + F90PPSuffixes: SCons.Tool.SourceFileScanner.add_scanner(suffix, F90Scan) +del suffix # fVLG = fortran.VariableListGenerator diff --git a/src/engine/SCons/Tool/f95.py b/src/engine/SCons/Tool/f95.py index 7adc80b..94786c8 100644 --- a/src/engine/SCons/Tool/f95.py +++ b/src/engine/SCons/Tool/f95.py @@ -53,6 +53,7 @@ F95Scan = SCons.Scanner.Fortran.FortranScan("F95PATH") for suffix in F95Suffixes + F95PPSuffixes: SCons.Tool.SourceFileScanner.add_scanner(suffix, F95Scan) +del suffix # fVLG = fortran.VariableListGenerator diff --git a/src/engine/SCons/Tool/fortran.py b/src/engine/SCons/Tool/fortran.py index b83e7d3..8494fd6 100644 --- a/src/engine/SCons/Tool/fortran.py +++ b/src/engine/SCons/Tool/fortran.py @@ -63,6 +63,7 @@ FortranScan = SCons.Scanner.Fortran.FortranScan("FORTRANPATH") for suffix in FortranSuffixes + FortranPPSuffixes: SCons.Tool.SourceFileScanner.add_scanner(suffix, FortranScan) +del suffix # def _fortranEmitter(target, source, env): diff --git a/src/engine/SCons/Tool/intelc.py b/src/engine/SCons/Tool/intelc.py index e668bf0..d95a4be 100644 --- a/src/engine/SCons/Tool/intelc.py +++ b/src/engine/SCons/Tool/intelc.py @@ -151,7 +151,7 @@ def get_intel_registry_value(valuename, version=None, abi=None): return v # or v.encode('iso-8859-1', 'replace') to remove unicode? except SCons.Util.RegError: raise MissingRegistryError, \ - "%s\\%s was not found in the registry."%(K, value) + "%s\\%s was not found in the registry."%(K, valuename) def get_all_compiler_versions(): diff --git a/src/engine/SCons/Tool/jar.py b/src/engine/SCons/Tool/jar.py index ed93412..cb0a8eb 100644 --- a/src/engine/SCons/Tool/jar.py +++ b/src/engine/SCons/Tool/jar.py @@ -35,11 +35,14 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import SCons.Action import SCons.Builder +import SCons.Subst import SCons.Util def jarSources(target, source, env, for_signature): """Only include sources that are not a manifest file.""" jarchdir = env.subst('$JARCHDIR') + if jarchdir: + jarchdir = env.fs.Dir(jarchdir) result = [] for src in source: contents = src.get_contents() @@ -47,7 +50,7 @@ def jarSources(target, source, env, for_signature): if jarchdir: # If we are changing the dir with -C, then sources should # be relative to that directory. - src = src.get_path(src.fs.Dir(jarchdir)) + src = SCons.Subst.Literal(src.get_path(jarchdir)) result.append('-C') result.append(jarchdir) result.append(src) diff --git a/src/engine/SCons/Tool/mingw.py b/src/engine/SCons/Tool/mingw.py index 0639535..cc7f584 100644 --- a/src/engine/SCons/Tool/mingw.py +++ b/src/engine/SCons/Tool/mingw.py @@ -39,6 +39,7 @@ import string import SCons.Action import SCons.Builder +import SCons.Defaults import SCons.Tool import SCons.Util diff --git a/src/engine/SCons/Tool/msvs.py b/src/engine/SCons/Tool/msvs.py index e35c92a..b84b277 100644 --- a/src/engine/SCons/Tool/msvs.py +++ b/src/engine/SCons/Tool/msvs.py @@ -205,7 +205,7 @@ class _DSPGenerator: if len(buildtarget) == 1: bt = buildtarget[0] buildtarget = [] - for v in variants: + for _ in variants: buildtarget.append(bt) if not env.has_key('outdir') or env['outdir'] == None: @@ -1010,7 +1010,6 @@ class _GenerateV7DSW(_DSWGenerator): self.file.write('\tGlobalSection(ProjectConfiguration) = postSolution\n') for name in confkeys: - name = name variant = self.configs[name].variant platform = self.configs[name].platform if self.version_num >= 8.0: @@ -1279,7 +1278,7 @@ def get_visualstudio8_suites(): try: idk = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, r'Software\Microsoft\VisualStudio\8.0') - id = SCons.Util.RegQueryValueEx(idk, 'InstallDir') + SCons.Util.RegQueryValueEx(idk, 'InstallDir') editions = { 'PRO': r'Setup\VS\Pro' } # ToDo: add standard and team editions edition_name = 'STD' for name, key_suffix in editions.items(): @@ -1297,7 +1296,7 @@ def get_visualstudio8_suites(): try: idk = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, r'Software\Microsoft\VCExpress\8.0') - id = SCons.Util.RegQueryValueEx(idk, 'InstallDir') + SCons.Util.RegQueryValueEx(idk, 'InstallDir') suites.append('EXPRESS') except SCons.Util.RegError: pass diff --git a/src/engine/SCons/Tool/mwcc.py b/src/engine/SCons/Tool/mwcc.py index a1ede44..0d5ce82 100644 --- a/src/engine/SCons/Tool/mwcc.py +++ b/src/engine/SCons/Tool/mwcc.py @@ -32,11 +32,11 @@ selection method. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" -import SCons.Util import os import os.path import string +import SCons.Util def set_vars(env): """Set MWCW_VERSION, MWCW_VERSIONS, and some codewarrior environment vars @@ -155,6 +155,8 @@ CXXSuffixes = ['.cc', '.cpp', '.cxx', '.c++', '.C++'] def generate(env): """Add Builders and construction variables for the mwcc to an Environment.""" + import SCons.Defaults + import SCons.Tool set_vars(env) diff --git a/src/engine/SCons/Tool/mwld.py b/src/engine/SCons/Tool/mwld.py index e2b1827..e73c730 100644 --- a/src/engine/SCons/Tool/mwld.py +++ b/src/engine/SCons/Tool/mwld.py @@ -33,7 +33,6 @@ selection method. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import SCons.Tool -import SCons.Tool.mwcc def generate(env): @@ -60,7 +59,8 @@ def generate(env): def exists(env): - return mwcc.set_versions(env) + import SCons.Tool.mwcc + return SCons.Tool.mwcc.set_vars(env) def shlib_generator(target, source, env, for_signature): diff --git a/src/engine/SCons/Tool/pdf.py b/src/engine/SCons/Tool/pdf.py index 0f6468b..b0cd126 100644 --- a/src/engine/SCons/Tool/pdf.py +++ b/src/engine/SCons/Tool/pdf.py @@ -36,7 +36,7 @@ PDFBuilder = None def generate(env): try: - bld = env['BUILDERS']['PDF'] + env['BUILDERS']['PDF'] except KeyError: global PDFBuilder if PDFBuilder is None: diff --git a/src/engine/SCons/Tool/qt.py b/src/engine/SCons/Tool/qt.py index 4d290c7..105f42e 100644 --- a/src/engine/SCons/Tool/qt.py +++ b/src/engine/SCons/Tool/qt.py @@ -75,7 +75,6 @@ def checkMocIncluded(target, source, env): (str(moc), str(cpp))) def find_file(filename, paths, node_factory): - retval = None for dir in paths: node = node_factory(filename, dir) if node.rexists(): @@ -219,7 +218,6 @@ def uicEmitter(target, source, env): return target, source def uicScannerFunc(node, env, path): - dir = node.dir lookout = [] lookout.extend(env['CPPPATH']) lookout.append(str(node.rfile().dir)) @@ -231,18 +229,17 @@ def uicScannerFunc(node, env, path): result.append(dep) return result -uicScanner = SCons.Scanner.Scanner(uicScannerFunc, - name = "UicScanner", - node_class = SCons.Node.FS.File, - node_factory = SCons.Node.FS.File, - recursive = 0) +uicScanner = SCons.Scanner.Base(uicScannerFunc, + name = "UicScanner", + node_class = SCons.Node.FS.File, + node_factory = SCons.Node.FS.File, + recursive = 0) def generate(env): """Add Builders and construction variables for qt to an Environment.""" CLVar = SCons.Util.CLVar Action = SCons.Action.Action Builder = SCons.Builder.Builder - splitext = SCons.Util.splitext env.SetDefault(QTDIR = _detect(env), QT_BINPATH = os.path.join('$QTDIR', 'bin'), diff --git a/src/engine/SCons/Tool/swig.py b/src/engine/SCons/Tool/swig.py index a8e12a2..04c3b2a 100644 --- a/src/engine/SCons/Tool/swig.py +++ b/src/engine/SCons/Tool/swig.py @@ -77,7 +77,6 @@ def recurse(path, searchPath): return found def _scanSwig(node, env, path): - import sys r = recurse(str(node), [os.path.abspath(os.path.dirname(str(node))), os.path.abspath(os.path.join("include", "swig"))]) return r @@ -85,7 +84,8 @@ def _swigEmitter(target, source, env): for src in source: src = str(src) mname = None - if "-python" in SCons.Util.CLVar(env.subst("$SWIGFLAGS")): + flags = SCons.Util.CLVar(env.subst("$SWIGFLAGS")) + if "-python" in flags and "-noproxy" not in flags: f = open(src) try: for l in f.readlines(): diff --git a/src/engine/SCons/Tool/tex.py b/src/engine/SCons/Tool/tex.py index 0329667..1defd90 100644 --- a/src/engine/SCons/Tool/tex.py +++ b/src/engine/SCons/Tool/tex.py @@ -112,7 +112,7 @@ def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None # Now decide if latex needs to be run yet again. logfilename = basename + '.log' - for trial in range(int(env.subst('$LATEXRETRIES'))): + for _ in range(int(env.subst('$LATEXRETRIES'))): if not os.path.exists(logfilename): break content = open(logfilename, "rb").read() diff --git a/src/engine/SCons/Tool/zip.py b/src/engine/SCons/Tool/zip.py index f0d4ed0..22f63fb 100644 --- a/src/engine/SCons/Tool/zip.py +++ b/src/engine/SCons/Tool/zip.py @@ -36,6 +36,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import os.path import SCons.Builder +import SCons.Defaults import SCons.Node.FS import SCons.Util diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py index 4bd050a..eb38e44 100644 --- a/src/engine/SCons/Util.py +++ b/src/engine/SCons/Util.py @@ -37,7 +37,6 @@ import os.path import re import string import sys -import stat import types from UserDict import UserDict @@ -234,9 +233,6 @@ def render_tree(root, child_func, prune=0, margin=[0], visited={}): rname = str(root) - if visited.has_key(rname): - return "" - children = child_func(root) retval = "" for pipe in margin[:-1]: @@ -245,6 +241,9 @@ def render_tree(root, child_func, prune=0, margin=[0], visited={}): else: retval = retval + " " + if visited.has_key(rname): + return retval + "+-[" + rname + "]\n" + retval = retval + "+-" + rname + "\n" if not prune: visited = copy.copy(visited) @@ -278,21 +277,19 @@ def print_tree(root, child_func, prune=0, showtags=0, margin=[0], visited={}): rname = str(root) - if visited.has_key(rname): - return - if showtags: if showtags == 2: - print ' E = exists' - print ' R = exists in repository only' - print ' b = implicit builder' - print ' B = explicit builder' - print ' S = side effect' - print ' P = precious' - print ' A = always build' - print ' C = current' - print ' N = no clean' + print ' E = exists' + print ' R = exists in repository only' + print ' b = implicit builder' + print ' B = explicit builder' + print ' S = side effect' + print ' P = precious' + print ' A = always build' + print ' C = current' + print ' N = no clean' + print ' H = no cache' print '' tags = ['['] @@ -305,6 +302,7 @@ def print_tree(root, child_func, prune=0, showtags=0, margin=[0], visited={}): tags.append(' A'[IDX(root.always_build)]) tags.append(' C'[IDX(root.current())]) tags.append(' N'[IDX(root.noclean)]) + tags.append(' H'[IDX(root.nocache)]) tags.append(']') else: @@ -314,6 +312,10 @@ def print_tree(root, child_func, prune=0, showtags=0, margin=[0], visited={}): return [" ","| "][m] margins = map(MMM, margin[:-1]) + if visited.has_key(rname): + print string.join(tags + margins + ['+-[', rname, ']'], '') + return + print string.join(tags + margins + ['+-', rname], '') if prune: @@ -567,6 +569,7 @@ elif os.name == 'os2': else: def WhereIs(file, path=None, pathext=None, reject=[]): + import stat if path is None: try: path = os.environ['PATH'] @@ -864,9 +867,10 @@ def unique(s): for x in s: u[x] = 1 except TypeError: - del u # move on to the next method + pass # move on to the next method else: return u.keys() + del u # We can't hash all the elements. Second fastest is to sort, # which brings the equal elements together; then duplicates are @@ -879,7 +883,7 @@ def unique(s): t = list(s) t.sort() except TypeError: - del t # move on to the next method + pass # move on to the next method else: assert n > 0 last = t[0] @@ -890,6 +894,7 @@ def unique(s): lasti = lasti + 1 i = i + 1 return t[:lasti] + del t # Brute force is all that's left. u = [] @@ -926,3 +931,16 @@ class LogicalLines: break result.append(line) return result + +class Unbuffered: + """ + A proxy class that wraps a file object, flushing after every write, + and delegating everything else to the wrapped object. + """ + def __init__(self, file): + self.file = file + def write(self, arg): + self.file.write(arg) + self.file.flush() + def __getattr__(self, attr): + return getattr(self.file, attr) diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py index e291662..db6d9f6 100644 --- a/src/engine/SCons/UtilTests.py +++ b/src/engine/SCons/UtilTests.py @@ -56,6 +56,7 @@ class UtilTestCase(unittest.TestCase): def __init__(self, name, children=[]): self.children = children self.name = name + self.nocache = None def __str__(self): return self.name def exists(self): @@ -100,7 +101,7 @@ class UtilTestCase(unittest.TestCase): """ lines = string.split(expect, '\n')[:-1] - lines = map(lambda l: '[E BSPACN]'+l, lines) + lines = map(lambda l: '[E BSPACN ]'+l, lines) withtags = string.join(lines, '\n') + '\n' return foo, expect, withtags @@ -120,10 +121,11 @@ class UtilTestCase(unittest.TestCase): +-blat.h | +-stdlib.h +-bar.h + +-[stdlib.h] """ lines = string.split(expect, '\n')[:-1] - lines = map(lambda l: '[E BSPACN]'+l, lines) + lines = map(lambda l: '[E BSPACN ]'+l, lines) withtags = string.join(lines, '\n') + '\n' return blat_o, expect, withtags diff --git a/src/engine/SCons/Warnings.py b/src/engine/SCons/Warnings.py index 1b13c96..b313ee0 100644 --- a/src/engine/SCons/Warnings.py +++ b/src/engine/SCons/Warnings.py @@ -35,6 +35,7 @@ class Warning(SCons.Errors.UserError): pass +# NOTE: If you add a new warning class, add it to the man page, too! class CacheWriteErrorWarning(Warning): pass @@ -51,6 +52,9 @@ class DeprecatedWarning(Warning): class DuplicateEnvironmentWarning(Warning): pass +class MisleadingKeywordsWarning(Warning): + pass + class MissingSConscriptWarning(Warning): pass @@ -66,9 +70,6 @@ class NoParallelSupportWarning(Warning): class ReservedVariableWarning(Warning): pass -class MisleadingKeywordsWarning(Warning): - pass - _warningAsException = 0 # The below is a list of 2-tuples. The first element is a class object. diff --git a/src/engine/SCons/__init__.py b/src/engine/SCons/__init__.py index 20e4734..b548841 100644 --- a/src/engine/SCons/__init__.py +++ b/src/engine/SCons/__init__.py @@ -38,5 +38,3 @@ __buildsys__ = "__BUILDSYS__" __date__ = "__DATE__" __developer__ = "__DEVELOPER__" - -import SCons.Memoize diff --git a/src/engine/SCons/compat/__init__.py b/src/engine/SCons/compat/__init__.py index 6112db5..7b74ab4 100644 --- a/src/engine/SCons/compat/__init__.py +++ b/src/engine/SCons/compat/__init__.py @@ -80,6 +80,25 @@ def import_as(module, name): import builtins try: + set +except NameError: + # Pre-2.4 Python has no native set type + try: + # Python 2.2 and 2.3 can use the copy of the 2.[45] sets module + # that we grabbed. + import_as('_sets', 'sets') + except (ImportError, SyntaxError): + # Python 1.5 (ImportError, no __future_ module) and 2.1 + # (SyntaxError, no generators in __future__) will blow up + # trying to import the 2.[45] sets module, so back off to a + # custom sets module that can be discarded easily when we + # stop supporting those versions. + import_as('_sets15', 'sets') + import __builtin__ + import sets + __builtin__.set = sets.Set + +try: import subprocess except ImportError: # Pre-2.4 Python has no subprocess module. diff --git a/src/engine/SCons/compat/_sets.py b/src/engine/SCons/compat/_sets.py new file mode 100644 index 0000000..32a0dd6 --- /dev/null +++ b/src/engine/SCons/compat/_sets.py @@ -0,0 +1,577 @@ +"""Classes to represent arbitrary sets (including sets of sets). + +This module implements sets using dictionaries whose values are +ignored. The usual operations (union, intersection, deletion, etc.) +are provided as both methods and operators. + +Important: sets are not sequences! While they support 'x in s', +'len(s)', and 'for x in s', none of those operations are unique for +sequences; for example, mappings support all three as well. The +characteristic operation for sequences is subscripting with small +integers: s[i], for i in range(len(s)). Sets don't support +subscripting at all. Also, sequences allow multiple occurrences and +their elements have a definite order; sets on the other hand don't +record multiple occurrences and don't remember the order of element +insertion (which is why they don't support s[i]). + +The following classes are provided: + +BaseSet -- All the operations common to both mutable and immutable + sets. This is an abstract class, not meant to be directly + instantiated. + +Set -- Mutable sets, subclass of BaseSet; not hashable. + +ImmutableSet -- Immutable sets, subclass of BaseSet; hashable. + An iterable argument is mandatory to create an ImmutableSet. + +_TemporarilyImmutableSet -- A wrapper around a Set, hashable, + giving the same hash value as the immutable set equivalent + would have. Do not use this class directly. + +Only hashable objects can be added to a Set. In particular, you cannot +really add a Set as an element to another Set; if you try, what is +actually added is an ImmutableSet built from it (it compares equal to +the one you tried adding). + +When you ask if `x in y' where x is a Set and y is a Set or +ImmutableSet, x is wrapped into a _TemporarilyImmutableSet z, and +what's tested is actually `z in y'. + +""" + +# Code history: +# +# - Greg V. Wilson wrote the first version, using a different approach +# to the mutable/immutable problem, and inheriting from dict. +# +# - Alex Martelli modified Greg's version to implement the current +# Set/ImmutableSet approach, and make the data an attribute. +# +# - Guido van Rossum rewrote much of the code, made some API changes, +# and cleaned up the docstrings. +# +# - Raymond Hettinger added a number of speedups and other +# improvements. + +from __future__ import generators +try: + from itertools import ifilter, ifilterfalse +except ImportError: + # Code to make the module run under Py2.2 + def ifilter(predicate, iterable): + if predicate is None: + def predicate(x): + return x + for x in iterable: + if predicate(x): + yield x + def ifilterfalse(predicate, iterable): + if predicate is None: + def predicate(x): + return x + for x in iterable: + if not predicate(x): + yield x + try: + True, False + except NameError: + True, False = (0==0, 0!=0) + +__all__ = ['BaseSet', 'Set', 'ImmutableSet'] + +class BaseSet(object): + """Common base class for mutable and immutable sets.""" + + __slots__ = ['_data'] + + # Constructor + + def __init__(self): + """This is an abstract class.""" + # Don't call this from a concrete subclass! + if self.__class__ is BaseSet: + raise TypeError, ("BaseSet is an abstract class. " + "Use Set or ImmutableSet.") + + # Standard protocols: __len__, __repr__, __str__, __iter__ + + def __len__(self): + """Return the number of elements of a set.""" + return len(self._data) + + def __repr__(self): + """Return string representation of a set. + + This looks like 'Set([<list of elements>])'. + """ + return self._repr() + + # __str__ is the same as __repr__ + __str__ = __repr__ + + def _repr(self, sorted=False): + elements = self._data.keys() + if sorted: + elements.sort() + return '%s(%r)' % (self.__class__.__name__, elements) + + def __iter__(self): + """Return an iterator over the elements or a set. + + This is the keys iterator for the underlying dict. + """ + return self._data.iterkeys() + + # Three-way comparison is not supported. However, because __eq__ is + # tried before __cmp__, if Set x == Set y, x.__eq__(y) returns True and + # then cmp(x, y) returns 0 (Python doesn't actually call __cmp__ in this + # case). + + def __cmp__(self, other): + raise TypeError, "can't compare sets using cmp()" + + # Equality comparisons using the underlying dicts. Mixed-type comparisons + # are allowed here, where Set == z for non-Set z always returns False, + # and Set != z always True. This allows expressions like "x in y" to + # give the expected result when y is a sequence of mixed types, not + # raising a pointless TypeError just because y contains a Set, or x is + # a Set and y contain's a non-set ("in" invokes only __eq__). + # Subtle: it would be nicer if __eq__ and __ne__ could return + # NotImplemented instead of True or False. Then the other comparand + # would get a chance to determine the result, and if the other comparand + # also returned NotImplemented then it would fall back to object address + # comparison (which would always return False for __eq__ and always + # True for __ne__). However, that doesn't work, because this type + # *also* implements __cmp__: if, e.g., __eq__ returns NotImplemented, + # Python tries __cmp__ next, and the __cmp__ here then raises TypeError. + + def __eq__(self, other): + if isinstance(other, BaseSet): + return self._data == other._data + else: + return False + + def __ne__(self, other): + if isinstance(other, BaseSet): + return self._data != other._data + else: + return True + + # Copying operations + + def copy(self): + """Return a shallow copy of a set.""" + result = self.__class__() + result._data.update(self._data) + return result + + __copy__ = copy # For the copy module + + def __deepcopy__(self, memo): + """Return a deep copy of a set; used by copy module.""" + # This pre-creates the result and inserts it in the memo + # early, in case the deep copy recurses into another reference + # to this same set. A set can't be an element of itself, but + # it can certainly contain an object that has a reference to + # itself. + from copy import deepcopy + result = self.__class__() + memo[id(self)] = result + data = result._data + value = True + for elt in self: + data[deepcopy(elt, memo)] = value + return result + + # Standard set operations: union, intersection, both differences. + # Each has an operator version (e.g. __or__, invoked with |) and a + # method version (e.g. union). + # Subtle: Each pair requires distinct code so that the outcome is + # correct when the type of other isn't suitable. For example, if + # we did "union = __or__" instead, then Set().union(3) would return + # NotImplemented instead of raising TypeError (albeit that *why* it + # raises TypeError as-is is also a bit subtle). + + def __or__(self, other): + """Return the union of two sets as a new set. + + (I.e. all elements that are in either set.) + """ + if not isinstance(other, BaseSet): + return NotImplemented + return self.union(other) + + def union(self, other): + """Return the union of two sets as a new set. + + (I.e. all elements that are in either set.) + """ + result = self.__class__(self) + result._update(other) + return result + + def __and__(self, other): + """Return the intersection of two sets as a new set. + + (I.e. all elements that are in both sets.) + """ + if not isinstance(other, BaseSet): + return NotImplemented + return self.intersection(other) + + def intersection(self, other): + """Return the intersection of two sets as a new set. + + (I.e. all elements that are in both sets.) + """ + if not isinstance(other, BaseSet): + other = Set(other) + if len(self) <= len(other): + little, big = self, other + else: + little, big = other, self + common = ifilter(big._data.has_key, little) + return self.__class__(common) + + def __xor__(self, other): + """Return the symmetric difference of two sets as a new set. + + (I.e. all elements that are in exactly one of the sets.) + """ + if not isinstance(other, BaseSet): + return NotImplemented + return self.symmetric_difference(other) + + def symmetric_difference(self, other): + """Return the symmetric difference of two sets as a new set. + + (I.e. all elements that are in exactly one of the sets.) + """ + result = self.__class__() + data = result._data + value = True + selfdata = self._data + try: + otherdata = other._data + except AttributeError: + otherdata = Set(other)._data + for elt in ifilterfalse(otherdata.has_key, selfdata): + data[elt] = value + for elt in ifilterfalse(selfdata.has_key, otherdata): + data[elt] = value + return result + + def __sub__(self, other): + """Return the difference of two sets as a new Set. + + (I.e. all elements that are in this set and not in the other.) + """ + if not isinstance(other, BaseSet): + return NotImplemented + return self.difference(other) + + def difference(self, other): + """Return the difference of two sets as a new Set. + + (I.e. all elements that are in this set and not in the other.) + """ + result = self.__class__() + data = result._data + try: + otherdata = other._data + except AttributeError: + otherdata = Set(other)._data + value = True + for elt in ifilterfalse(otherdata.has_key, self): + data[elt] = value + return result + + # Membership test + + def __contains__(self, element): + """Report whether an element is a member of a set. + + (Called in response to the expression `element in self'.) + """ + try: + return element in self._data + except TypeError: + transform = getattr(element, "__as_temporarily_immutable__", None) + if transform is None: + raise # re-raise the TypeError exception we caught + return transform() in self._data + + # Subset and superset test + + def issubset(self, other): + """Report whether another set contains this set.""" + self._binary_sanity_check(other) + if len(self) > len(other): # Fast check for obvious cases + return False + for elt in ifilterfalse(other._data.has_key, self): + return False + return True + + def issuperset(self, other): + """Report whether this set contains another set.""" + self._binary_sanity_check(other) + if len(self) < len(other): # Fast check for obvious cases + return False + for elt in ifilterfalse(self._data.has_key, other): + return False + return True + + # Inequality comparisons using the is-subset relation. + __le__ = issubset + __ge__ = issuperset + + def __lt__(self, other): + self._binary_sanity_check(other) + return len(self) < len(other) and self.issubset(other) + + def __gt__(self, other): + self._binary_sanity_check(other) + return len(self) > len(other) and self.issuperset(other) + + # Assorted helpers + + def _binary_sanity_check(self, other): + # Check that the other argument to a binary operation is also + # a set, raising a TypeError otherwise. + if not isinstance(other, BaseSet): + raise TypeError, "Binary operation only permitted between sets" + + def _compute_hash(self): + # Calculate hash code for a set by xor'ing the hash codes of + # the elements. This ensures that the hash code does not depend + # on the order in which elements are added to the set. This is + # not called __hash__ because a BaseSet should not be hashable; + # only an ImmutableSet is hashable. + result = 0 + for elt in self: + result ^= hash(elt) + return result + + def _update(self, iterable): + # The main loop for update() and the subclass __init__() methods. + data = self._data + + # Use the fast update() method when a dictionary is available. + if isinstance(iterable, BaseSet): + data.update(iterable._data) + return + + value = True + + if type(iterable) in (list, tuple, xrange): + # Optimized: we know that __iter__() and next() can't + # raise TypeError, so we can move 'try:' out of the loop. + it = iter(iterable) + while True: + try: + for element in it: + data[element] = value + return + except TypeError: + transform = getattr(element, "__as_immutable__", None) + if transform is None: + raise # re-raise the TypeError exception we caught + data[transform()] = value + else: + # Safe: only catch TypeError where intended + for element in iterable: + try: + data[element] = value + except TypeError: + transform = getattr(element, "__as_immutable__", None) + if transform is None: + raise # re-raise the TypeError exception we caught + data[transform()] = value + + +class ImmutableSet(BaseSet): + """Immutable set class.""" + + __slots__ = ['_hashcode'] + + # BaseSet + hashing + + def __init__(self, iterable=None): + """Construct an immutable set from an optional iterable.""" + self._hashcode = None + self._data = {} + if iterable is not None: + self._update(iterable) + + def __hash__(self): + if self._hashcode is None: + self._hashcode = self._compute_hash() + return self._hashcode + + def __getstate__(self): + return self._data, self._hashcode + + def __setstate__(self, state): + self._data, self._hashcode = state + +class Set(BaseSet): + """ Mutable set class.""" + + __slots__ = [] + + # BaseSet + operations requiring mutability; no hashing + + def __init__(self, iterable=None): + """Construct a set from an optional iterable.""" + self._data = {} + if iterable is not None: + self._update(iterable) + + def __getstate__(self): + # getstate's results are ignored if it is not + return self._data, + + def __setstate__(self, data): + self._data, = data + + def __hash__(self): + """A Set cannot be hashed.""" + # We inherit object.__hash__, so we must deny this explicitly + raise TypeError, "Can't hash a Set, only an ImmutableSet." + + # In-place union, intersection, differences. + # Subtle: The xyz_update() functions deliberately return None, + # as do all mutating operations on built-in container types. + # The __xyz__ spellings have to return self, though. + + def __ior__(self, other): + """Update a set with the union of itself and another.""" + self._binary_sanity_check(other) + self._data.update(other._data) + return self + + def union_update(self, other): + """Update a set with the union of itself and another.""" + self._update(other) + + def __iand__(self, other): + """Update a set with the intersection of itself and another.""" + self._binary_sanity_check(other) + self._data = (self & other)._data + return self + + def intersection_update(self, other): + """Update a set with the intersection of itself and another.""" + if isinstance(other, BaseSet): + self &= other + else: + self._data = (self.intersection(other))._data + + def __ixor__(self, other): + """Update a set with the symmetric difference of itself and another.""" + self._binary_sanity_check(other) + self.symmetric_difference_update(other) + return self + + def symmetric_difference_update(self, other): + """Update a set with the symmetric difference of itself and another.""" + data = self._data + value = True + if not isinstance(other, BaseSet): + other = Set(other) + if self is other: + self.clear() + for elt in other: + if elt in data: + del data[elt] + else: + data[elt] = value + + def __isub__(self, other): + """Remove all elements of another set from this set.""" + self._binary_sanity_check(other) + self.difference_update(other) + return self + + def difference_update(self, other): + """Remove all elements of another set from this set.""" + data = self._data + if not isinstance(other, BaseSet): + other = Set(other) + if self is other: + self.clear() + for elt in ifilter(data.has_key, other): + del data[elt] + + # Python dict-like mass mutations: update, clear + + def update(self, iterable): + """Add all values from an iterable (such as a list or file).""" + self._update(iterable) + + def clear(self): + """Remove all elements from this set.""" + self._data.clear() + + # Single-element mutations: add, remove, discard + + def add(self, element): + """Add an element to a set. + + This has no effect if the element is already present. + """ + try: + self._data[element] = True + except TypeError: + transform = getattr(element, "__as_immutable__", None) + if transform is None: + raise # re-raise the TypeError exception we caught + self._data[transform()] = True + + def remove(self, element): + """Remove an element from a set; it must be a member. + + If the element is not a member, raise a KeyError. + """ + try: + del self._data[element] + except TypeError: + transform = getattr(element, "__as_temporarily_immutable__", None) + if transform is None: + raise # re-raise the TypeError exception we caught + del self._data[transform()] + + def discard(self, element): + """Remove an element from a set if it is a member. + + If the element is not a member, do nothing. + """ + try: + self.remove(element) + except KeyError: + pass + + def pop(self): + """Remove and return an arbitrary set element.""" + return self._data.popitem()[0] + + def __as_immutable__(self): + # Return a copy of self as an immutable set + return ImmutableSet(self) + + def __as_temporarily_immutable__(self): + # Return self wrapped in a temporarily immutable set + return _TemporarilyImmutableSet(self) + + +class _TemporarilyImmutableSet(BaseSet): + # Wrap a mutable set as if it was temporarily immutable. + # This only supplies hashing and equality comparisons. + + def __init__(self, set): + self._set = set + self._data = set._data # Needed by ImmutableSet.__eq__() + + def __hash__(self): + return self._set._compute_hash() diff --git a/src/engine/SCons/compat/_sets15.py b/src/engine/SCons/compat/_sets15.py new file mode 100644 index 0000000..b3d0bb3 --- /dev/null +++ b/src/engine/SCons/compat/_sets15.py @@ -0,0 +1,159 @@ +# +# A Set class that works all the way back to Python 1.5. From: +# +# Python Cookbook: Yet another Set class for Python +# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/106469 +# Goncalo Rodriques +# +# This is a pure Pythonic implementation of a set class. The syntax +# and methods implemented are, for the most part, borrowed from +# PEP 218 by Greg Wilson. +# +# Note that this class violates the formal definition of a set() by adding +# a __getitem__() method so we can iterate over a set's elements under +# Python 1.5 and 2.1, which don't support __iter__() and iterator types. +# + +import string + +class Set: + """The set class. It can contain mutable objects.""" + + def __init__(self, seq = None): + """The constructor. It can take any object giving an iterator as an optional + argument to populate the new set.""" + self.elems = [] + if seq: + for elem in seq: + if elem not in self.elems: + self.elems.append(elem) + + def __str__(self): + return "{%s}" % string.join(map(str, self.elems), ", ") + + + def copy(self): + """Shallow copy of a set object.""" + return Set(self.elems) + + def __contains__(self, elem): + return elem in self.elems + + def __len__(self): + return len(self.elems) + + def __getitem__(self, index): + # Added so that Python 1.5 can iterate over the elements. + # The cookbook recipe's author didn't like this because there + # really isn't any order in a set object, but this is necessary + # to make the class work well enough for our purposes. + return self.elems[index] + + def items(self): + """Returns a list of the elements in the set.""" + return self.elems + + def add(self, elem): + """Add one element to the set.""" + if elem not in self.elems: + self.elems.append(elem) + + def remove(self, elem): + """Remove an element from the set. Return an error if elem is not in the set.""" + try: + self.elems.remove(elem) + except ValueError: + raise LookupError, "Object %s is not a member of the set." % str(elem) + + def discard(self, elem): + """Remove an element from the set. Do nothing if elem is not in the set.""" + try: + self.elems.remove(elem) + except ValueError: + pass + + def sort(self, func=cmp): + self.elems.sort(func) + + #Define an iterator for a set. + def __iter__(self): + return iter(self.elems) + + #The basic binary operations with sets. + def __or__(self, other): + """Union of two sets.""" + ret = self.copy() + for elem in other.elems: + if elem not in ret: + ret.elems.append(elem) + return ret + + def __sub__(self, other): + """Difference of two sets.""" + ret = self.copy() + for elem in other.elems: + ret.discard(elem) + return ret + + def __and__(self, other): + """Intersection of two sets.""" + ret = Set() + for elem in self.elems: + if elem in other.elems: + ret.elems.append(elem) + return ret + + def __add__(self, other): + """Symmetric difference of two sets.""" + ret = Set() + temp = other.copy() + for elem in self.elems: + if elem in temp.elems: + temp.elems.remove(elem) + else: + ret.elems.append(elem) + #Add remaining elements. + for elem in temp.elems: + ret.elems.append(elem) + return ret + + def __mul__(self, other): + """Cartesian product of two sets.""" + ret = Set() + for elemself in self.elems: + x = map(lambda other, s=elemself: (s, other), other.elems) + ret.elems.extend(x) + return ret + + #Some of the binary comparisons. + def __lt__(self, other): + """Returns 1 if the lhs set is contained but not equal to the rhs set.""" + if len(self.elems) < len(other.elems): + temp = other.copy() + for elem in self.elems: + if elem in temp.elems: + temp.remove(elem) + else: + return 0 + return len(temp.elems) == 0 + else: + return 0 + + def __le__(self, other): + """Returns 1 if the lhs set is contained in the rhs set.""" + if len(self.elems) <= len(other.elems): + ret = 1 + for elem in self.elems: + if elem not in other.elems: + ret = 0 + break + return ret + else: + return 0 + + def __eq__(self, other): + """Returns 1 if the sets are equal.""" + if len(self.elems) != len(other.elems): + return 0 + else: + return len(self - other) == 0 diff --git a/src/engine/SCons/compat/_subprocess.py b/src/engine/SCons/compat/_subprocess.py index aa17cae..fc06347 100644 --- a/src/engine/SCons/compat/_subprocess.py +++ b/src/engine/SCons/compat/_subprocess.py @@ -356,6 +356,7 @@ import sys mswindows = (sys.platform == "win32") import os +import string import types import traceback @@ -565,7 +566,7 @@ def list2cmdline(seq): result.extend(bs_buf) result.append('"') - return ''.join(result) + return string.join(result, '') try: @@ -1048,12 +1049,8 @@ class Popen(object): # Close pipe fds. Make sure we don't close the same # fd more than once, or standard fds. - if p2cread: - os.close(p2cread) - if c2pwrite and c2pwrite not in (p2cread,): - os.close(c2pwrite) - if errwrite and errwrite not in (p2cread, c2pwrite): - os.close(errwrite) + for fd in set((p2cread, c2pwrite, errwrite))-set((0,1,2)): + if fd: os.close(fd) # Close all other fds, if asked for if close_fds: @@ -1079,7 +1076,7 @@ class Popen(object): exc_lines = traceback.format_exception(exc_type, exc_value, tb) - exc_value.child_traceback = ''.join(exc_lines) + exc_value.child_traceback = string.join(exc_lines, '') os.write(errpipe_write, pickle.dumps(exc_value)) # This exitcode won't be reported to applications, so it @@ -1158,6 +1155,7 @@ class Popen(object): read_set.append(self.stderr) stderr = [] + input_offset = 0 while read_set or write_set: rlist, wlist, xlist = select.select(read_set, write_set, []) @@ -1165,9 +1163,9 @@ class Popen(object): # When select has indicated that the file is writable, # we can write up to PIPE_BUF bytes without risk # blocking. POSIX defines PIPE_BUF >= 512 - bytes_written = os.write(self.stdin.fileno(), input[:512]) - input = input[bytes_written:] - if not input: + bytes_written = os.write(self.stdin.fileno(), buffer(input, input_offset, 512)) + input_offset = input_offset + bytes_written + if input_offset >= len(input): self.stdin.close() write_set.remove(self.stdin) @@ -1187,9 +1185,9 @@ class Popen(object): # All data exchanged. Translate lists into strings. if stdout is not None: - stdout = ''.join(stdout) + stdout = string.join(stdout, '') if stderr is not None: - stderr = ''.join(stderr) + stderr = string.join(stderr, '') # Translate newlines, if requested. We cannot let the file # object do the translation: It is based on stdio, which is diff --git a/src/engine/SCons/cpp.py b/src/engine/SCons/cpp.py index d96ef23..8620936 100644 --- a/src/engine/SCons/cpp.py +++ b/src/engine/SCons/cpp.py @@ -32,7 +32,6 @@ import SCons.compat import os import re import string -import sys # # First "subsystem" of regular expressions that we set up: |