diff options
Diffstat (limited to 'src/engine/SCons')
41 files changed, 1433 insertions, 768 deletions
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index eecea11..5a34825 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -540,7 +540,7 @@ class _ActionAction(ActionBase): if chdir: save_cwd = os.getcwd() try: - chdir = str(chdir.abspath) + chdir = str(chdir.get_abspath()) except AttributeError: if not is_String(chdir): if executor: diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index c82f6ac..769b15d 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -352,11 +352,6 @@ class BuilderBase(object): nodes (files) from input nodes (files). """ - if SCons.Memoize.use_memoizer: - __metaclass__ = SCons.Memoize.Memoized_Metaclass - - memoizer_counters = [] - def __init__(self, action = None, prefix = '', suffix = '', @@ -758,8 +753,7 @@ class BuilderBase(object): def _get_src_builders_key(self, env): return id(env) - memoizer_counters.append(SCons.Memoize.CountDict('get_src_builders', _get_src_builders_key)) - + @SCons.Memoize.CountDictCall(_get_src_builders_key) def get_src_builders(self, env): """ Returns the list of source Builders for this Builder. @@ -795,8 +789,7 @@ class BuilderBase(object): def _subst_src_suffixes_key(self, env): return id(env) - memoizer_counters.append(SCons.Memoize.CountDict('subst_src_suffixes', _subst_src_suffixes_key)) - + @SCons.Memoize.CountDictCall(_subst_src_suffixes_key) def subst_src_suffixes(self, env): """ The suffix list may contain construction variable expansions, diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py index 70a7a3f..3eca588 100644 --- a/src/engine/SCons/BuilderTests.py +++ b/src/engine/SCons/BuilderTests.py @@ -163,7 +163,8 @@ class MyNode_without_target_from_source(object): self.builder = None self.is_explicit = None self.side_effect = 0 - self.suffix = os.path.splitext(name)[1] + def get_suffix(self): + return os.path.splitext(self.name)[1] def disambiguate(self): return self def __str__(self): @@ -349,7 +350,7 @@ class BuilderTestCase(unittest.TestCase): builder = SCons.Builder.Builder(action="foo") target = builder(env, None, source='n22', srcdir='src_dir')[0] - p = target.sources[0].path + p = target.sources[0].get_internal_path() assert p == os.path.join('src_dir', 'n22'), p def test_mistaken_variables(self): @@ -487,20 +488,20 @@ class BuilderTestCase(unittest.TestCase): builder = SCons.Builder.Builder(prefix = 'lib', action='') assert builder.get_prefix(env) == 'lib' tgt = builder(env, target = 'tgt1', source = 'src1')[0] - assert tgt.path == 'libtgt1', \ - "Target has unexpected name: %s" % tgt.path + assert tgt.get_internal_path() == 'libtgt1', \ + "Target has unexpected name: %s" % tgt.get_internal_path() tgt = builder(env, target = 'tgt2a tgt2b', source = 'src2')[0] - assert tgt.path == 'libtgt2a tgt2b', \ - "Target has unexpected name: %s" % tgt.path + assert tgt.get_internal_path() == 'libtgt2a tgt2b', \ + "Target has unexpected name: %s" % tgt.get_internal_path() tgt = builder(env, target = None, source = 'src3')[0] - assert tgt.path == 'libsrc3', \ - "Target has unexpected name: %s" % tgt.path + assert tgt.get_internal_path() == 'libsrc3', \ + "Target has unexpected name: %s" % tgt.get_internal_path() tgt = builder(env, target = None, source = 'lib/src4')[0] - assert tgt.path == os.path.join('lib', 'libsrc4'), \ - "Target has unexpected name: %s" % tgt.path + assert tgt.get_internal_path() == os.path.join('lib', 'libsrc4'), \ + "Target has unexpected name: %s" % tgt.get_internal_path() tgt = builder(env, target = 'lib/tgt5', source = 'lib/src5')[0] - assert tgt.path == os.path.join('lib', 'libtgt5'), \ - "Target has unexpected name: %s" % tgt.path + assert tgt.get_internal_path() == os.path.join('lib', 'libtgt5'), \ + "Target has unexpected name: %s" % tgt.get_internal_path() def gen_prefix(env, sources): return "gen_prefix() says " + env['FOO'] @@ -520,17 +521,17 @@ class BuilderTestCase(unittest.TestCase): '.zzz' : my_emit}, action = '') tgt = builder(my_env, target = None, source = 'f1')[0] - assert tgt.path == 'default-f1', tgt.path + assert tgt.get_internal_path() == 'default-f1', tgt.get_internal_path() tgt = builder(my_env, target = None, source = 'f2.c')[0] - assert tgt.path == 'default-f2', tgt.path + assert tgt.get_internal_path() == 'default-f2', tgt.get_internal_path() tgt = builder(my_env, target = None, source = 'f3.in')[0] - assert tgt.path == 'out-f3', tgt.path + assert tgt.get_internal_path() == 'out-f3', tgt.get_internal_path() tgt = builder(my_env, target = None, source = 'f4.x')[0] - assert tgt.path == 'y-f4', tgt.path + assert tgt.get_internal_path() == 'y-f4', tgt.get_internal_path() tgt = builder(my_env, target = None, source = 'f5.foo')[0] - assert tgt.path == 'foo-f5', tgt.path + assert tgt.get_internal_path() == 'foo-f5', tgt.get_internal_path() tgt = builder(my_env, target = None, source = 'f6.zzz')[0] - assert tgt.path == 'emit-f6', tgt.path + assert tgt.get_internal_path() == 'emit-f6', tgt.get_internal_path() def test_set_suffix(self): """Test the set_suffix() method""" @@ -560,13 +561,13 @@ class BuilderTestCase(unittest.TestCase): assert b1.src_suffixes(env) == ['.c'], b1.src_suffixes(env) tgt = b1(env, target = 'tgt2', source = 'src2')[0] - assert tgt.sources[0].path == 'src2.c', \ - "Source has unexpected name: %s" % tgt.sources[0].path + assert tgt.sources[0].get_internal_path() == 'src2.c', \ + "Source has unexpected name: %s" % tgt.sources[0].get_internal_path() tgt = b1(env, target = 'tgt3', source = 'src3a src3b')[0] assert len(tgt.sources) == 1 - assert tgt.sources[0].path == 'src3a src3b.c', \ - "Unexpected tgt.sources[0] name: %s" % tgt.sources[0].path + assert tgt.sources[0].get_internal_path() == 'src3a src3b.c', \ + "Unexpected tgt.sources[0] name: %s" % tgt.sources[0].get_internal_path() b2 = SCons.Builder.Builder(src_suffix = '.2', src_builder = b1) r = sorted(b2.src_suffixes(env)) @@ -636,14 +637,14 @@ class BuilderTestCase(unittest.TestCase): builder = SCons.Builder.Builder(suffix = 'o', action='') assert builder.get_suffix(env) == '.o', builder.get_suffix(env) tgt = builder(env, target = 'tgt3', source = 'src3')[0] - assert tgt.path == 'tgt3.o', \ - "Target has unexpected name: %s" % tgt.path + assert tgt.get_internal_path() == 'tgt3.o', \ + "Target has unexpected name: %s" % tgt.get_internal_path() tgt = builder(env, target = 'tgt4a tgt4b', source = 'src4')[0] - assert tgt.path == 'tgt4a tgt4b.o', \ - "Target has unexpected name: %s" % tgt.path + assert tgt.get_internal_path() == 'tgt4a tgt4b.o', \ + "Target has unexpected name: %s" % tgt.get_internal_path() tgt = builder(env, target = None, source = 'src5')[0] - assert tgt.path == 'src5.o', \ - "Target has unexpected name: %s" % tgt.path + assert tgt.get_internal_path() == 'src5.o', \ + "Target has unexpected name: %s" % tgt.get_internal_path() def gen_suffix(env, sources): return "gen_suffix() says " + env['BAR'] @@ -663,17 +664,17 @@ class BuilderTestCase(unittest.TestCase): '.zzz' : my_emit}, action='') tgt = builder(my_env, target = None, source = 'f1')[0] - assert tgt.path == 'f1.default', tgt.path + assert tgt.get_internal_path() == 'f1.default', tgt.get_internal_path() tgt = builder(my_env, target = None, source = 'f2.c')[0] - assert tgt.path == 'f2.default', tgt.path + assert tgt.get_internal_path() == 'f2.default', tgt.get_internal_path() tgt = builder(my_env, target = None, source = 'f3.in')[0] - assert tgt.path == 'f3.out', tgt.path + assert tgt.get_internal_path() == 'f3.out', tgt.get_internal_path() tgt = builder(my_env, target = None, source = 'f4.x')[0] - assert tgt.path == 'f4.y', tgt.path + assert tgt.get_internal_path() == 'f4.y', tgt.get_internal_path() tgt = builder(my_env, target = None, source = 'f5.bar')[0] - assert tgt.path == 'f5.new', tgt.path + assert tgt.get_internal_path() == 'f5.new', tgt.get_internal_path() tgt = builder(my_env, target = None, source = 'f6.zzz')[0] - assert tgt.path == 'f6.emit', tgt.path + assert tgt.get_internal_path() == 'f6.emit', tgt.get_internal_path() def test_single_source(self): """Test Builder with single_source flag set""" diff --git a/src/engine/SCons/CacheDir.py b/src/engine/SCons/CacheDir.py index 9dd18e5..f32b326 100644 --- a/src/engine/SCons/CacheDir.py +++ b/src/engine/SCons/CacheDir.py @@ -50,11 +50,11 @@ def CacheRetrieveFunc(target, source, env): cd.CacheDebug('CacheRetrieve(%s): retrieving from %s\n', t, cachefile) if SCons.Action.execute_actions: if fs.islink(cachefile): - fs.symlink(fs.readlink(cachefile), t.path) + fs.symlink(fs.readlink(cachefile), t.get_internal_path()) else: - env.copy_from_cache(cachefile, t.path) + env.copy_from_cache(cachefile, t.get_internal_path()) st = fs.stat(cachefile) - fs.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE) + fs.chmod(t.get_internal_path(), stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE) return 0 def CacheRetrieveString(target, source, env): @@ -63,7 +63,7 @@ def CacheRetrieveString(target, source, env): cd = env.get_CacheDir() cachedir, cachefile = cd.cachepath(t) if t.fs.exists(cachefile): - return "Retrieved `%s' from cache" % t.path + return "Retrieved `%s' from cache" % t.get_internal_path() return None CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString) @@ -106,12 +106,12 @@ def CachePushFunc(target, source, env): raise SCons.Errors.EnvironmentError(msg) try: - if fs.islink(t.path): - fs.symlink(fs.readlink(t.path), tempfile) + if fs.islink(t.get_internal_path()): + fs.symlink(fs.readlink(t.get_internal_path()), tempfile) else: - fs.copy2(t.path, tempfile) + fs.copy2(t.get_internal_path(), tempfile) fs.rename(tempfile, cachefile) - st = fs.stat(t.path) + st = fs.stat(t.get_internal_path()) fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE) except EnvironmentError: # It's possible someone else tried writing the file at the diff --git a/src/engine/SCons/Debug.py b/src/engine/SCons/Debug.py index 9974039..b47c24c 100644 --- a/src/engine/SCons/Debug.py +++ b/src/engine/SCons/Debug.py @@ -34,6 +34,7 @@ import os import sys import time import weakref +import inspect # Global variable that gets set to 'True' by the Main script, # when the creation of class instances should get tracked. @@ -46,7 +47,12 @@ def logInstanceCreation(instance, name=None): name = instance.__class__.__name__ if name not in tracked_classes: tracked_classes[name] = [] - tracked_classes[name].append(weakref.ref(instance)) + if hasattr(instance, '__dict__'): + tracked_classes[name].append(weakref.ref(instance)) + else: + # weakref doesn't seem to work when the instance + # contains only slots... + tracked_classes[name].append(instance) def string_to_classes(s): if s == '*': @@ -66,7 +72,10 @@ def listLoggedInstances(classes, file=sys.stdout): for classname in string_to_classes(classes): file.write('\n%s:\n' % classname) for ref in tracked_classes[classname]: - obj = ref() + if inspect.isclass(ref): + obj = ref() + else: + obj = ref if obj is not None: file.write(' %s\n' % repr(obj)) diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index 5f2c9ff..fa019b8 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -365,9 +365,6 @@ class SubstitutionEnvironment(object): class actually becomes useful.) """ - if SCons.Memoize.use_memoizer: - __metaclass__ = SCons.Memoize.Memoized_Metaclass - def __init__(self, **kw): """Initialization of an underlying SubstitutionEnvironment class. """ @@ -902,8 +899,6 @@ class Base(SubstitutionEnvironment): Environment. """ - memoizer_counters = [] - ####################################################################### # This is THE class for interacting with the SCons build engine, # and it contains a lot of stuff, so we're going to try to keep this @@ -1071,8 +1066,7 @@ class Base(SubstitutionEnvironment): factory = getattr(self.fs, name) return factory - memoizer_counters.append(SCons.Memoize.CountValue('_gsm')) - + @SCons.Memoize.CountMethodCall def _gsm(self): try: return self._memo['_gsm'] @@ -1802,7 +1796,7 @@ class Base(SubstitutionEnvironment): self.Replace(**kw) def _find_toolpath_dir(self, tp): - return self.fs.Dir(self.subst(tp)).srcnode().abspath + return self.fs.Dir(self.subst(tp)).srcnode().get_abspath() def Tool(self, tool, toolpath=None, **kw): if SCons.Util.is_String(tool): diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py index a0869e8..9a9d2b0 100644 --- a/src/engine/SCons/EnvironmentTests.py +++ b/src/engine/SCons/EnvironmentTests.py @@ -2703,25 +2703,25 @@ def generate(env): t = env.AlwaysBuild('a', 'b$FOO', ['c', 'd'], '$BAR', env.fs.Dir('dir'), env.fs.File('file')) assert t[0].__class__.__name__ == 'Entry' - assert t[0].path == 'a' + assert t[0].get_internal_path() == 'a' assert t[0].always_build assert t[1].__class__.__name__ == 'Entry' - assert t[1].path == 'bfff' + assert t[1].get_internal_path() == 'bfff' assert t[1].always_build assert t[2].__class__.__name__ == 'Entry' - assert t[2].path == 'c' + assert t[2].get_internal_path() == 'c' assert t[2].always_build assert t[3].__class__.__name__ == 'Entry' - assert t[3].path == 'd' + assert t[3].get_internal_path() == 'd' assert t[3].always_build assert t[4].__class__.__name__ == 'Entry' - assert t[4].path == 'bbb' + assert t[4].get_internal_path() == 'bbb' assert t[4].always_build assert t[5].__class__.__name__ == 'Dir' - assert t[5].path == 'dir' + assert t[5].get_internal_path() == 'dir' assert t[5].always_build assert t[6].__class__.__name__ == 'File' - assert t[6].path == 'file' + assert t[6].get_internal_path() == 'file' assert t[6].always_build def test_VariantDir(self): @@ -2811,13 +2811,13 @@ def generate(env): assert t.builder is not None assert t.builder.action.__class__.__name__ == 'CommandAction' assert t.builder.action.cmd_list == 'buildfoo $target $source' - assert 'foo1.in' in [x.path for x in t.sources] - assert 'foo2.in' in [x.path for x in t.sources] + assert 'foo1.in' in [x.get_internal_path() for x in t.sources] + assert 'foo2.in' in [x.get_internal_path() for x in t.sources] sub = env.fs.Dir('sub') t = env.Command(target='bar.out', source='sub', action='buildbar $target $source')[0] - assert 'sub' in [x.path for x in t.sources] + assert 'sub' in [x.get_internal_path() for x in t.sources] def testFunc(env, target, source): assert str(target[0]) == 'foo.out' @@ -2828,8 +2828,8 @@ def generate(env): assert t.builder is not None assert t.builder.action.__class__.__name__ == 'FunctionAction' t.build() - assert 'foo1.in' in [x.path for x in t.sources] - assert 'foo2.in' in [x.path for x in t.sources] + assert 'foo1.in' in [x.get_internal_path() for x in t.sources] + assert 'foo2.in' in [x.get_internal_path() for x in t.sources] x = [] def test2(baz, x=x): @@ -2846,7 +2846,7 @@ def generate(env): action = 'foo', X = 'xxx')[0] assert str(t) == 'xxx.out', str(t) - assert 'xxx.in' in [x.path for x in t.sources] + assert 'xxx.in' in [x.get_internal_path() for x in t.sources] env = self.TestEnvironment(source_scanner = 'should_not_find_this') t = env.Command(target='file.out', source='file.in', @@ -2890,27 +2890,27 @@ def generate(env): t = env.Depends(target='EnvironmentTest.py', dependency='Environment.py')[0] assert t.__class__.__name__ == 'Entry', t.__class__.__name__ - assert t.path == 'EnvironmentTest.py' + assert t.get_internal_path() == 'EnvironmentTest.py' assert len(t.depends) == 1 d = t.depends[0] assert d.__class__.__name__ == 'Entry', d.__class__.__name__ - assert d.path == 'Environment.py' + assert d.get_internal_path() == 'Environment.py' t = env.Depends(target='${FOO}.py', dependency='${BAR}.py')[0] assert t.__class__.__name__ == 'File', t.__class__.__name__ - assert t.path == 'xxx.py' + assert t.get_internal_path() == 'xxx.py' assert len(t.depends) == 1 d = t.depends[0] assert d.__class__.__name__ == 'File', d.__class__.__name__ - assert d.path == 'yyy.py' + assert d.get_internal_path() == 'yyy.py' t = env.Depends(target='dir1', dependency='dir2')[0] assert t.__class__.__name__ == 'Dir', t.__class__.__name__ - assert t.path == 'dir1' + assert t.get_internal_path() == 'dir1' assert len(t.depends) == 1 d = t.depends[0] assert d.__class__.__name__ == 'Dir', d.__class__.__name__ - assert d.path == 'dir2' + assert d.get_internal_path() == 'dir2' def test_Dir(self): """Test the Dir() method""" @@ -2944,19 +2944,19 @@ def generate(env): t = env.NoClean('p_a', 'p_${BAR}b', ['p_c', 'p_d'], 'p_$FOO') assert t[0].__class__.__name__ == 'Entry', t[0].__class__.__name__ - assert t[0].path == 'p_a' + assert t[0].get_internal_path() == 'p_a' assert t[0].noclean assert t[1].__class__.__name__ == 'Dir', t[1].__class__.__name__ - assert t[1].path == 'p_hhhb' + assert t[1].get_internal_path() == 'p_hhhb' assert t[1].noclean assert t[2].__class__.__name__ == 'Entry', t[2].__class__.__name__ - assert t[2].path == 'p_c' + assert t[2].get_internal_path() == 'p_c' assert t[2].noclean assert t[3].__class__.__name__ == 'File', t[3].__class__.__name__ - assert t[3].path == 'p_d' + assert t[3].get_internal_path() == 'p_d' assert t[3].noclean assert t[4].__class__.__name__ == 'Entry', t[4].__class__.__name__ - assert t[4].path == 'p_ggg' + assert t[4].get_internal_path() == 'p_ggg' assert t[4].noclean def test_Dump(self): @@ -3074,27 +3074,27 @@ def generate(env): t = env.Ignore(target='targ.py', dependency='dep.py')[0] assert t.__class__.__name__ == 'Entry', t.__class__.__name__ - assert t.path == 'targ.py' + assert t.get_internal_path() == 'targ.py' assert len(t.ignore) == 1 i = t.ignore[0] assert i.__class__.__name__ == 'Entry', i.__class__.__name__ - assert i.path == 'dep.py' + assert i.get_internal_path() == 'dep.py' t = env.Ignore(target='$FOO$BAR', dependency='$BAR$FOO')[0] assert t.__class__.__name__ == 'File', t.__class__.__name__ - assert t.path == 'yyyzzz' + assert t.get_internal_path() == 'yyyzzz' assert len(t.ignore) == 1 i = t.ignore[0] assert i.__class__.__name__ == 'File', i.__class__.__name__ - assert i.path == 'zzzyyy' + assert i.get_internal_path() == 'zzzyyy' t = env.Ignore(target='dir1', dependency='dir2')[0] assert t.__class__.__name__ == 'Dir', t.__class__.__name__ - assert t.path == 'dir1' + assert t.get_internal_path() == 'dir1' assert len(t.ignore) == 1 i = t.ignore[0] assert i.__class__.__name__ == 'Dir', i.__class__.__name__ - assert i.path == 'dir2' + assert i.get_internal_path() == 'dir2' def test_Literal(self): """Test the Literal() method""" @@ -3123,19 +3123,19 @@ def generate(env): t = env.Precious('p_a', 'p_${BAR}b', ['p_c', 'p_d'], 'p_$FOO') assert t[0].__class__.__name__ == 'Entry', t[0].__class__.__name__ - assert t[0].path == 'p_a' + assert t[0].get_internal_path() == 'p_a' assert t[0].precious assert t[1].__class__.__name__ == 'Dir', t[1].__class__.__name__ - assert t[1].path == 'p_hhhb' + assert t[1].get_internal_path() == 'p_hhhb' assert t[1].precious assert t[2].__class__.__name__ == 'Entry', t[2].__class__.__name__ - assert t[2].path == 'p_c' + assert t[2].get_internal_path() == 'p_c' assert t[2].precious assert t[3].__class__.__name__ == 'File', t[3].__class__.__name__ - assert t[3].path == 'p_d' + assert t[3].get_internal_path() == 'p_d' assert t[3].precious assert t[4].__class__.__name__ == 'Entry', t[4].__class__.__name__ - assert t[4].path == 'p_ggg' + assert t[4].get_internal_path() == 'p_ggg' assert t[4].precious def test_Pseudo(self): @@ -3146,19 +3146,19 @@ def generate(env): t = env.Pseudo('p_a', 'p_${BAR}b', ['p_c', 'p_d'], 'p_$FOO') assert t[0].__class__.__name__ == 'Entry', t[0].__class__.__name__ - assert t[0].path == 'p_a' + assert t[0].get_internal_path() == 'p_a' assert t[0].pseudo assert t[1].__class__.__name__ == 'Dir', t[1].__class__.__name__ - assert t[1].path == 'p_hhhb' + assert t[1].get_internal_path() == 'p_hhhb' assert t[1].pseudo assert t[2].__class__.__name__ == 'Entry', t[2].__class__.__name__ - assert t[2].path == 'p_c' + assert t[2].get_internal_path() == 'p_c' assert t[2].pseudo assert t[3].__class__.__name__ == 'File', t[3].__class__.__name__ - assert t[3].path == 'p_d' + assert t[3].get_internal_path() == 'p_d' assert t[3].pseudo assert t[4].__class__.__name__ == 'Entry', t[4].__class__.__name__ - assert t[4].path == 'p_ggg' + assert t[4].get_internal_path() == 'p_ggg' assert t[4].pseudo def test_Repository(self): @@ -3263,7 +3263,7 @@ def generate(env): bar = env.Object('bar.obj', 'bar.cpp')[0] s = env.SideEffect('mylib.pdb', ['foo.obj', 'bar.obj'])[0] assert s.__class__.__name__ == 'Entry', s.__class__.__name__ - assert s.path == 'mylib.pdb' + assert s.get_internal_path() == 'mylib.pdb' assert s.side_effect assert foo.side_effects == [s] assert bar.side_effects == [s] @@ -3272,7 +3272,7 @@ def generate(env): bbb = env.Object('bbb.obj', 'bbb.cpp')[0] s = env.SideEffect('my${LIB}.pdb', ['${FOO}.obj', '${BAR}.obj'])[0] assert s.__class__.__name__ == 'File', s.__class__.__name__ - assert s.path == 'mylll.pdb' + assert s.get_internal_path() == 'mylll.pdb' assert s.side_effect assert fff.side_effects == [s], fff.side_effects assert bbb.side_effects == [s], bbb.side_effects @@ -3281,7 +3281,7 @@ def generate(env): ccc = env.Object('ccc.obj', 'ccc.cpp')[0] s = env.SideEffect('mymmm.pdb', ['ggg.obj', 'ccc.obj'])[0] assert s.__class__.__name__ == 'Dir', s.__class__.__name__ - assert s.path == 'mymmm.pdb' + assert s.get_internal_path() == 'mymmm.pdb' assert s.side_effect assert ggg.side_effects == [s], ggg.side_effects assert ccc.side_effects == [s], ccc.side_effects @@ -3290,18 +3290,18 @@ def generate(env): """Test the SourceCode() method.""" env = self.TestEnvironment(FOO='mmm', BAR='nnn') e = env.SourceCode('foo', None)[0] - assert e.path == 'foo' + assert e.get_internal_path() == 'foo' s = e.src_builder() assert s is None, s b = Builder() e = env.SourceCode(e, b)[0] - assert e.path == 'foo' + assert e.get_internal_path() == 'foo' s = e.src_builder() assert s is b, s e = env.SourceCode('$BAR$FOO', None)[0] - assert e.path == 'nnnmmm' + assert e.get_internal_path() == 'nnnmmm' s = e.src_builder() assert s is None, s diff --git a/src/engine/SCons/Executor.py b/src/engine/SCons/Executor.py index 388f8ac..98ed758 100644 --- a/src/engine/SCons/Executor.py +++ b/src/engine/SCons/Executor.py @@ -40,6 +40,10 @@ import SCons.Memoize class Batch(object): """Remembers exact association between targets and sources of executor.""" + + __slots__ = ('targets', + 'sources') + def __init__(self, targets=[], sources=[]): self.targets = targets self.sources = sources @@ -109,6 +113,48 @@ def rfile(node): return rfile() +def execute_nothing(obj, target, kw): + return 0 + +def execute_action_list(obj, target, kw): + """Actually execute the action list.""" + env = obj.get_build_env() + kw = obj.get_kw(kw) + status = 0 + for act in obj.get_action_list(): + #args = (self.get_all_targets(), self.get_all_sources(), env) + args = ([], [], env) + status = act(*args, **kw) + if isinstance(status, SCons.Errors.BuildError): + status.executor = obj + raise status + elif status: + msg = "Error %s" % status + raise SCons.Errors.BuildError( + errstr=msg, + node=obj.batches[0].targets, + executor=obj, + action=act) + return status + +_do_execute_map = {0 : execute_nothing, + 1 : execute_action_list} + + +def execute_actions_str(obj): + env = obj.get_build_env() + return "\n".join([action.genstring(obj.get_all_targets(), + obj.get_all_sources(), + env) + for action in obj.get_action_list()]) + +def execute_null_str(obj): + return '' + +_execute_str_map = {0 : execute_null_str, + 1 : execute_actions_str} + + class Executor(object): """A class for controlling instances of executing an action. @@ -117,10 +163,21 @@ class Executor(object): and sources for later processing as needed. """ - if SCons.Memoize.use_memoizer: - __metaclass__ = SCons.Memoize.Memoized_Metaclass - - memoizer_counters = [] + __slots__ = ('pre_actions', + 'post_actions', + 'env', + 'overridelist', + 'batches', + 'builder_kw', + '_memo', + 'lvars', + '_changed_sources_list', + '_changed_targets_list', + '_unchanged_sources_list', + '_unchanged_targets_list', + 'action_list', + '_do_execute', + '_execute_str') def __init__(self, action, env=None, overridelist=[{}], targets=[], sources=[], builder_kw={}): @@ -135,6 +192,8 @@ class Executor(object): else: self.batches = [] self.builder_kw = builder_kw + self._do_execute = 1 + self._execute_str = 1 self._memo = {} def get_lvars(self): @@ -284,8 +343,7 @@ class Executor(object): result.extend(target.side_effects) return result - memoizer_counters.append(SCons.Memoize.CountValue('get_build_env')) - + @SCons.Memoize.CountMethodCall def get_build_env(self): """Fetch or create the appropriate build Environment for this Executor. @@ -330,36 +388,12 @@ class Executor(object): result['executor'] = self return result - def do_nothing(self, target, kw): - return 0 - - def do_execute(self, target, kw): - """Actually execute the action list.""" - env = self.get_build_env() - kw = self.get_kw(kw) - status = 0 - for act in self.get_action_list(): - #args = (self.get_all_targets(), self.get_all_sources(), env) - args = ([], [], env) - status = act(*args, **kw) - if isinstance(status, SCons.Errors.BuildError): - status.executor = self - raise status - elif status: - msg = "Error %s" % status - raise SCons.Errors.BuildError( - errstr=msg, - node=self.batches[0].targets, - executor=self, - action=act) - return status - # use extra indirection because with new-style objects (Python 2.2 # and above) we can't override special methods, and nullify() needs # to be able to do this. def __call__(self, target, **kw): - return self.do_execute(target, kw) + return _do_execute_map[self._do_execute](self, target, kw) def cleanup(self): self._memo = {} @@ -403,24 +437,15 @@ class Executor(object): # another extra indirection for new-style objects and nullify... - def my_str(self): - env = self.get_build_env() - return "\n".join([action.genstring(self.get_all_targets(), - self.get_all_sources(), - env) - for action in self.get_action_list()]) - - def __str__(self): - return self.my_str() + return _execute_str_map[self._execute_str](self) def nullify(self): self.cleanup() - self.do_execute = self.do_nothing - self.my_str = lambda: '' - - memoizer_counters.append(SCons.Memoize.CountValue('get_contents')) + self._do_execute = 0 + self._execute_str = 0 + @SCons.Memoize.CountMethodCall def get_contents(self): """Fetch the signature contents. This is the main reason this class exists, so we can compute this once and cache it regardless @@ -493,8 +518,7 @@ class Executor(object): def _get_unignored_sources_key(self, node, ignore=()): return (node,) + tuple(ignore) - memoizer_counters.append(SCons.Memoize.CountDict('get_unignored_sources', _get_unignored_sources_key)) - + @SCons.Memoize.CountDictCall(_get_unignored_sources_key) def get_unignored_sources(self, node, ignore=()): key = (node,) + tuple(ignore) try: @@ -579,6 +603,23 @@ class Null(object): disassociate Builders from Nodes entirely, so we're not going to worry about unit tests for this--at least for now. """ + + __slots__ = ('pre_actions', + 'post_actions', + 'env', + 'overridelist', + 'batches', + 'builder_kw', + '_memo', + 'lvars', + '_changed_sources_list', + '_changed_targets_list', + '_unchanged_sources_list', + '_unchanged_targets_list', + 'action_list', + '_do_execute', + '_execute_str') + def __init__(self, *args, **kw): if SCons.Debug.track_instances: logInstanceCreation(self, 'Executor.Null') self.batches = [Batch(kw['targets'][:], [])] diff --git a/src/engine/SCons/Memoize.py b/src/engine/SCons/Memoize.py index e77aacf..5144f83 100644 --- a/src/engine/SCons/Memoize.py +++ b/src/engine/SCons/Memoize.py @@ -25,17 +25,17 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" __doc__ = """Memoizer -A metaclass implementation to count hits and misses of the computed +A decorator-based implementation to count hits and misses of the computed values that various methods cache in memory. Use of this modules assumes that wrapped methods be coded to cache their -values in a consistent way. Here is an example of wrapping a method -that returns a computed value, with no input parameters: +values in a consistent way. In particular, it requires that the class uses a +dictionary named "_memo" to store the cached values. - memoizer_counters = [] # Memoization - - memoizer_counters.append(SCons.Memoize.CountValue('foo')) # Memoization +Here is an example of wrapping a method that returns a computed value, +with no input parameters: + @SCons.Memoize.CountMethodCall def foo(self): try: # Memoization @@ -55,8 +55,7 @@ based on one or more input arguments: def _bar_key(self, argument): # Memoization return argument # Memoization - memoizer_counters.append(SCons.Memoize.CountDict('bar', _bar_key)) # Memoization - + @SCons.Memoize.CountDictCall(_bar_key) def bar(self, argument): memo_key = argument # Memoization @@ -77,10 +76,6 @@ based on one or more input arguments: return result -At one point we avoided replicating this sort of logic in all the methods -by putting it right into this module, but we've moved away from that at -present (see the "Historical Note," below.). - Deciding what to cache is tricky, because different configurations can have radically different performance tradeoffs, and because the tradeoffs involved are often so non-obvious. Consequently, deciding @@ -102,51 +97,37 @@ cache return values from a method that's being called a lot: input arguments, you don't need to use all of the arguments if some of them don't affect the return values. -Historical Note: The initial Memoizer implementation actually handled -the caching of values for the wrapped methods, based on a set of generic -algorithms for computing hashable values based on the method's arguments. -This collected caching logic nicely, but had two drawbacks: - - Running arguments through a generic key-conversion mechanism is slower - (and less flexible) than just coding these things directly. Since the - methods that need memoized values are generally performance-critical, - slowing them down in order to collect the logic isn't the right - tradeoff. - - Use of the memoizer really obscured what was being called, because - all the memoized methods were wrapped with re-used generic methods. - This made it more difficult, for example, to use the Python profiler - to figure out how to optimize the underlying methods. """ -import types - # A flag controlling whether or not we actually use memoization. use_memoizer = None -CounterList = [] +# Global list of counter objects +CounterList = {} class Counter(object): """ Base class for counting memoization hits and misses. - We expect that the metaclass initialization will have filled in - the .name attribute that represents the name of the function - being counted. + We expect that the initialization in a matching decorator will + fill in the correct class name and method name that represents + the name of the function being counted. """ - def __init__(self, method_name): + def __init__(self, cls_name, method_name): """ """ + self.cls_name = cls_name self.method_name = method_name self.hit = 0 self.miss = 0 - CounterList.append(self) + def key(self): + return self.cls_name+'.'+self.method_name def display(self): fmt = " %7d hits %7d misses %s()" - print fmt % (self.hit, self.miss, self.name) + print fmt % (self.hit, self.miss, self.key()) def __cmp__(self, other): try: - return cmp(self.name, other.name) + return cmp(self.key(), other.key()) except AttributeError: return 0 @@ -154,45 +135,39 @@ class CountValue(Counter): """ A counter class for simple, atomic memoized values. - A CountValue object should be instantiated in a class for each of + A CountValue object should be instantiated in a decorator for each of the class's methods that memoizes its return value by simply storing the return value in its _memo dictionary. - - We expect that the metaclass initialization will fill in the - .underlying_method attribute with the method that we're wrapping. - We then call the underlying_method method after counting whether - its memoized value has already been set (a hit) or not (a miss). """ - def __call__(self, *args, **kw): + def count(self, *args, **kw): + """ Counts whether the memoized value has already been + set (a hit) or not (a miss). + """ obj = args[0] if self.method_name in obj._memo: self.hit = self.hit + 1 else: self.miss = self.miss + 1 - return self.underlying_method(*args, **kw) class CountDict(Counter): """ A counter class for memoized values stored in a dictionary, with keys based on the method's input arguments. - A CountDict object is instantiated in a class for each of the + A CountDict object is instantiated in a decorator for each of the class's methods that memoizes its return value in a dictionary, indexed by some key that can be computed from one or more of its input arguments. - - We expect that the metaclass initialization will fill in the - .underlying_method attribute with the method that we're wrapping. - We then call the underlying_method method after counting whether the - computed key value is already present in the memoization dictionary - (a hit) or not (a miss). """ - def __init__(self, method_name, keymaker): + def __init__(self, cls_name, method_name, keymaker): """ """ - Counter.__init__(self, method_name) + Counter.__init__(self, cls_name, method_name) self.keymaker = keymaker - def __call__(self, *args, **kw): + def count(self, *args, **kw): + """ Counts whether the computed key value is already present + in the memoization dictionary (a hit) or not (a miss). + """ obj = args[0] try: memo_dict = obj._memo[self.method_name] @@ -204,39 +179,65 @@ class CountDict(Counter): self.hit = self.hit + 1 else: self.miss = self.miss + 1 - return self.underlying_method(*args, **kw) - -class Memoizer(object): - """Object which performs caching of method calls for its 'primary' - instance.""" - - def __init__(self): - pass def Dump(title=None): + """ Dump the hit/miss count for all the counters + collected so far. + """ if title: print title - CounterList.sort() - for counter in CounterList: - counter.display() - -class Memoized_Metaclass(type): - def __init__(cls, name, bases, cls_dict): - super(Memoized_Metaclass, cls).__init__(name, bases, cls_dict) - - for counter in cls_dict.get('memoizer_counters', []): - method_name = counter.method_name - - counter.name = cls.__name__ + '.' + method_name - counter.underlying_method = cls_dict[method_name] - - replacement_method = types.MethodType(counter, None, cls) - setattr(cls, method_name, replacement_method) + for counter in sorted(CounterList): + CounterList[counter].display() def EnableMemoization(): global use_memoizer use_memoizer = 1 +def CountMethodCall(fn): + """ Decorator for counting memoizer hits/misses while retrieving + a simple value in a class method. It wraps the given method + fn and uses a CountValue object to keep track of the + caching statistics. + Wrapping gets enabled by calling EnableMemoization(). + """ + if use_memoizer: + def wrapper(self, *args, **kwargs): + global CounterList + key = self.__class__.__name__+'.'+fn.__name__ + if key not in CounterList: + CounterList[key] = CountValue(self.__class__.__name__, fn.__name__) + CounterList[key].count(self, *args, **kwargs) + return fn(self, *args, **kwargs) + wrapper.__name__= fn.__name__ + return wrapper + else: + return fn + +def CountDictCall(keyfunc): + """ Decorator for counting memoizer hits/misses while accessing + dictionary values with a key-generating function. Like + CountMethodCall above, it wraps the given method + fn and uses a CountDict object to keep track of the + caching statistics. The dict-key function keyfunc has to + get passed in the decorator call and gets stored in the + CountDict instance. + Wrapping gets enabled by calling EnableMemoization(). + """ + def decorator(fn): + if use_memoizer: + def wrapper(self, *args, **kwargs): + global CounterList + key = self.__class__.__name__+'.'+fn.__name__ + if key not in CounterList: + CounterList[key] = CountDict(self.__class__.__name__, fn.__name__, keyfunc) + CounterList[key].count(self, *args, **kwargs) + return fn(self, *args, **kwargs) + wrapper.__name__= fn.__name__ + return wrapper + else: + return fn + return decorator + # Local Variables: # tab-width:4 # indent-tabs-mode:nil diff --git a/src/engine/SCons/MemoizeTests.py b/src/engine/SCons/MemoizeTests.py index 3606d57..77ff6bc 100644 --- a/src/engine/SCons/MemoizeTests.py +++ b/src/engine/SCons/MemoizeTests.py @@ -30,22 +30,18 @@ import TestUnit import SCons.Memoize - +# Enable memoization counting +SCons.Memoize.EnableMemoization() class FakeObject(object): - __metaclass__ = SCons.Memoize.Memoized_Metaclass - - memoizer_counters = [] - def __init__(self): self._memo = {} def _dict_key(self, argument): return argument - memoizer_counters.append(SCons.Memoize.CountDict('dict', _dict_key)) - + @SCons.Memoize.CountDictCall(_dict_key) def dict(self, argument): memo_key = argument @@ -66,8 +62,7 @@ class FakeObject(object): return result - memoizer_counters.append(SCons.Memoize.CountValue('value')) - + @SCons.Memoize.CountMethodCall def value(self): try: @@ -82,10 +77,7 @@ class FakeObject(object): return result def get_memoizer_counter(self, name): - for mc in self.memoizer_counters: - if mc.method_name == name: - return mc - return None + return SCons.Memoize.CounterList.get(self.__class__.__name__+'.'+name, None) class Returner(object): def __init__(self, result): diff --git a/src/engine/SCons/Node/Alias.py b/src/engine/SCons/Node/Alias.py index f817356..a035816 100644 --- a/src/engine/SCons/Node/Alias.py +++ b/src/engine/SCons/Node/Alias.py @@ -56,13 +56,47 @@ class AliasNameSpace(collections.UserDict): return None class AliasNodeInfo(SCons.Node.NodeInfoBase): - current_version_id = 1 + __slots__ = ('csig',) + current_version_id = 2 field_list = ['csig'] def str_to_node(self, s): return default_ans.Alias(s) + def __getstate__(self): + """ + Return all fields that shall be pickled. Walk the slots in the class + hierarchy and add those to the state dictionary. If a '__dict__' slot is + available, copy all entries to the dictionary. Also include the version + id, which is fixed for all instances of a class. + """ + state = getattr(self, '__dict__', {}).copy() + for obj in type(self).mro(): + for name in getattr(obj,'__slots__',()): + if hasattr(self, name): + state[name] = getattr(self, name) + + state['_version_id'] = self.current_version_id + try: + del state['__weakref__'] + except KeyError: + pass + + return state + + def __setstate__(self, state): + """ + Restore the attributes from a pickled state. + """ + # TODO check or discard version + del state['_version_id'] + for key, value in state.items(): + if key not in ('__weakref__',): + setattr(self, key, value) + + class AliasBuildInfo(SCons.Node.BuildInfoBase): - current_version_id = 1 + __slots__ = () + current_version_id = 2 class Alias(SCons.Node.Node): @@ -72,7 +106,9 @@ class Alias(SCons.Node.Node): def __init__(self, name): SCons.Node.Node.__init__(self) self.name = name - + self.changed_since_last_build = 1 + self.store_info = 0 + def str_for_display(self): return '"' + self.__str__() + '"' @@ -105,13 +141,6 @@ class Alias(SCons.Node.Node): # # - def changed_since_last_build(self, target, prev_ni): - cur_csig = self.get_csig() - try: - return cur_csig != prev_ni.csig - except AttributeError: - return 1 - def build(self): """A "builder" for aliases.""" pass diff --git a/src/engine/SCons/Node/AliasTests.py b/src/engine/SCons/Node/AliasTests.py index 2d11bdf..8e31875 100644 --- a/src/engine/SCons/Node/AliasTests.py +++ b/src/engine/SCons/Node/AliasTests.py @@ -103,14 +103,14 @@ class AliasNodeInfoTestCase(unittest.TestCase): """Test AliasNodeInfo initialization""" ans = SCons.Node.Alias.AliasNameSpace() aaa = ans.Alias('aaa') - ni = SCons.Node.Alias.AliasNodeInfo(aaa) + ni = SCons.Node.Alias.AliasNodeInfo() class AliasBuildInfoTestCase(unittest.TestCase): def test___init__(self): """Test AliasBuildInfo initialization""" ans = SCons.Node.Alias.AliasNameSpace() aaa = ans.Alias('aaa') - bi = SCons.Node.Alias.AliasBuildInfo(aaa) + bi = SCons.Node.Alias.AliasBuildInfo() if __name__ == "__main__": suite = unittest.TestSuite() diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index 4db1cb3..2402fac 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -56,10 +56,23 @@ import SCons.Warnings from SCons.Debug import Trace -do_store_info = True print_duplicate = 0 +def sconsign_none(node): + raise NotImplementedError + +def sconsign_dir(node): + """Return the .sconsign file info for this directory, + creating it first if necessary.""" + if not node._sconsign: + import SCons.SConsign + node._sconsign = SCons.SConsign.ForDirectory(node) + return node._sconsign + +_sconsign_map = {0 : sconsign_none, + 1 : sconsign_dir} + class EntryProxyAttributeError(AttributeError): """ An AttributeError subclass for recording and displaying the name @@ -268,8 +281,8 @@ def LinkFunc(target, source, env): # who want to move their soft-linked src-trees around. Those # people should use the 'hard-copy' mode, softlinks cannot be # used for that; at least I have no idea how ... - src = source[0].abspath - dest = target[0].abspath + src = source[0].get_abspath() + dest = target[0].get_abspath() dir, file = os.path.split(dest) if dir and not target[0].fs.isdir(dir): os.makedirs(dir) @@ -302,7 +315,7 @@ LocalCopy = SCons.Action.Action(LinkFunc, LocalString) def UnlinkFunc(target, source, env): t = target[0] - t.fs.unlink(t.abspath) + t.fs.unlink(t.get_abspath()) return 0 Unlink = SCons.Action.Action(UnlinkFunc, None) @@ -310,7 +323,7 @@ Unlink = SCons.Action.Action(UnlinkFunc, None) def MkdirFunc(target, source, env): t = target[0] if not t.exists(): - t.fs.mkdir(t.abspath) + t.fs.mkdir(t.get_abspath()) return 0 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None) @@ -403,7 +416,7 @@ def do_diskcheck_match(node, predicate, errorfmt): except (AttributeError, KeyError): pass if result: - raise TypeError(errorfmt % node.abspath) + raise TypeError(errorfmt % node.get_abspath()) def ignore_diskcheck_match(node, predicate, errorfmt): pass @@ -573,7 +586,20 @@ class Base(SCons.Node.Node): object identity comparisons. """ - memoizer_counters = [] + __slots__ = ['name', + 'fs', + 'abspath', + 'labspath', + 'path', + 'tpath', + 'path_elements', + 'dir', + 'cwd', + 'duplicate', + '_local', + 'sbuilder', + '_proxy', + '_func_sconsign'] def __init__(self, name, directory, fs): """Initialize a generic Node.FS.Base object. @@ -591,27 +617,26 @@ class Base(SCons.Node.Node): #: Filename with extension as it was specified when the object was #: created; to obtain filesystem path, use Python str() function self.name = SCons.Util.silent_intern(name) - #: Cached filename extension - self.suffix = SCons.Util.silent_intern(SCons.Util.splitext(name)[1]) self.fs = fs #: Reference to parent Node.FS object assert directory, "A directory must be provided" - self.abspath = SCons.Util.silent_intern(directory.entry_abspath(name)) - self.labspath = SCons.Util.silent_intern(directory.entry_labspath(name)) - if directory.path == '.': - self.path = SCons.Util.silent_intern(name) - else: - self.path = SCons.Util.silent_intern(directory.entry_path(name)) - if directory.tpath == '.': - self.tpath = SCons.Util.silent_intern(name) - else: - self.tpath = SCons.Util.silent_intern(directory.entry_tpath(name)) - self.path_elements = directory.path_elements + [self] + self.abspath = None + self.labspath = None + self.path = None + self.tpath = None + self.path_elements = None self.dir = directory self.cwd = None # will hold the SConscript directory for target nodes self.duplicate = directory.duplicate + self.changed_since_last_build = 2 + self._func_sconsign = 0 + self._func_exists = 2 + self._func_rexists = 2 + self._func_get_contents = 0 + self._func_target_from_source = 1 + self.store_info = 1 def str_for_display(self): return '"' + self.__str__() + '"' @@ -624,13 +649,13 @@ class Base(SCons.Node.Node): if isinstance(self, klass) or klass is Entry: return raise TypeError("Tried to lookup %s '%s' as a %s." %\ - (self.__class__.__name__, self.path, klass.__name__)) + (self.__class__.__name__, self.get_internal_path(), klass.__name__)) def get_dir(self): return self.dir def get_suffix(self): - return self.suffix + return SCons.Util.splitext(self.name)[1] def rfile(self): return self @@ -643,8 +668,7 @@ class Base(SCons.Node.Node): return self._save_str() return self._get_str() - memoizer_counters.append(SCons.Memoize.CountValue('_save_str')) - + @SCons.Memoize.CountMethodCall def _save_str(self): try: return self._memo['_save_str'] @@ -681,21 +705,20 @@ class Base(SCons.Node.Node): rstr = __str__ - memoizer_counters.append(SCons.Memoize.CountValue('stat')) - + @SCons.Memoize.CountMethodCall def stat(self): try: return self._memo['stat'] except KeyError: pass - try: result = self.fs.stat(self.abspath) + try: result = self.fs.stat(self.get_abspath()) except os.error: result = None self._memo['stat'] = result return result def exists(self): - return self.stat() is not None + return SCons.Node._exists_map[self._func_exists](self) def rexists(self): - return self.rfile().exists() + return SCons.Node._rexists_map[self._func_rexists](self) def getmtime(self): st = self.stat() @@ -717,7 +740,7 @@ class Base(SCons.Node.Node): if hasattr(os, 'symlink'): def islink(self): - try: st = self.fs.lstat(self.abspath) + try: st = self.fs.lstat(self.get_abspath()) except os.error: return 0 return stat.S_ISLNK(st[stat.ST_MODE]) else: @@ -752,7 +775,7 @@ class Base(SCons.Node.Node): dir = self.fs.getcwd() if self == dir: return '.' - path_elems = self.path_elements + path_elems = self.get_path_elements() pathname = '' try: i = path_elems.index(dir) except ValueError: @@ -785,7 +808,26 @@ class Base(SCons.Node.Node): def get_abspath(self): """Get the absolute path of the file.""" - return self.abspath + return self.dir.entry_abspath(self.name) + + def get_labspath(self): + """Get the absolute path of the file.""" + return self.dir.entry_labspath(self.name) + + def get_internal_path(self): + if self.dir.path == '.': + return self.name + else: + return self.dir.entry_path(self.name) + + def get_tpath(self): + if self.dir.tpath == '.': + return self.name + else: + return self.dir.entry_tpath(self.name) + + def get_path_elements(self): + return self.dir.path_elements + [self] def for_signature(self): # Return just our name. Even an absolute path would not work, @@ -811,13 +853,12 @@ class Base(SCons.Node.Node): files that need different behavior. See Tool/swig.py for an example. """ - return self.dir.Entry(prefix + splitext(self.name)[0] + suffix) + return SCons.Node._target_from_source_map[self._func_target_from_source](self, prefix, suffix, splitext) def _Rfindalldirs_key(self, pathlist): return pathlist - memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key)) - + @SCons.Memoize.CountDictCall(_Rfindalldirs_key) def Rfindalldirs(self, pathlist): """ Return all of the directories for a given path list, including @@ -856,8 +897,7 @@ class Base(SCons.Node.Node): cwd = self.cwd or self.fs._cwd return cwd.Rfindalldirs(pathlist) - memoizer_counters.append(SCons.Memoize.CountValue('rentry')) - + @SCons.Memoize.CountMethodCall def rentry(self): try: return self._memo['rentry'] @@ -878,7 +918,7 @@ class Base(SCons.Node.Node): def _glob1(self, pattern, ondisk=True, source=False, strings=False): return [] - + class Entry(Base): """This is the class for generic Node.FS entries--that is, things that could be a File or a Dir, but we're just not sure yet. @@ -887,6 +927,28 @@ class Entry(Base): time comes, and then call the same-named method in the transformed class.""" + __slots__ = ['scanner_paths', + 'cachedir_csig', + 'cachesig', + 'repositories', + 'srcdir', + 'entries', + 'searched', + '_sconsign', + 'variant_dirs', + 'root', + 'dirname', + 'on_disk_entries', + 'sccs_dir', + 'rcs_dir', + 'released_target_info', + 'contentsig'] + + def __init__(self, name, directory, fs): + Base.__init__(self, name, directory, fs) + self._func_exists = 3 + self._func_get_contents = 1 + def diskcheck_match(self): pass @@ -917,7 +979,7 @@ class Entry(Base): self.__class__ = Dir self._morph() elif must_exist: - msg = "No such file or directory: '%s'" % self.abspath + msg = "No such file or directory: '%s'" % self.get_abspath() raise SCons.Errors.UserError(msg) else: self.__class__ = File @@ -939,17 +1001,7 @@ class Entry(Base): def get_contents(self): """Fetch the contents of the entry. Returns the exact binary contents of the file.""" - try: - self = self.disambiguate(must_exist=1) - 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 - # like (e.g. in qt.py) don't have to disambiguate by hand - # or catch the exception. - return '' - else: - return self.get_contents() + return SCons.Node._get_contents_map[self._func_get_contents](self) def get_text_contents(self): """Fetch the decoded text contents of a Unicode encoded Entry. @@ -989,10 +1041,7 @@ class Entry(Base): # to make various tests pass. def exists(self): - """Return if the Entry exists. Check the file system to see - what we should turn into first. Assume a file if there's no - directory.""" - return self.disambiguate().exists() + return SCons.Node._exists_map[self._func_exists](self) def rel_path(self, other): d = self.disambiguate() @@ -1003,9 +1052,6 @@ class Entry(Base): def new_ninfo(self): return self.disambiguate().new_ninfo() - def changed_since_last_build(self, target, prev_ni): - return self.disambiguate().changed_since_last_build(target, prev_ni) - def _glob1(self, pattern, ondisk=True, source=False, strings=False): return self.disambiguate()._glob1(pattern, ondisk, source, strings) @@ -1019,9 +1065,6 @@ _classEntry = Entry class LocalFS(object): - if SCons.Memoize.use_memoizer: - __metaclass__ = SCons.Memoize.Memoized_Metaclass - # This class implements an abstraction layer for operations involving # a local file system. Essentially, this wraps any function in # the os, os.path or shutil modules that we use to actually go do @@ -1101,8 +1144,6 @@ class LocalFS(object): class FS(LocalFS): - memoizer_counters = [] - def __init__(self, path = None): """Initialize the Node.FS subsystem. @@ -1160,7 +1201,7 @@ class FS(LocalFS): if dir is not None: self._cwd = dir if change_os_dir: - os.chdir(dir.abspath) + os.chdir(dir.get_abspath()) except OSError: self._cwd = curr raise @@ -1249,9 +1290,9 @@ class FS(LocalFS): # The path is relative to the top-level SCons directory. if p in ('', '.'): - p = directory.labspath + p = directory.get_labspath() else: - p = directory.labspath + '/' + p + p = directory.get_labspath() + '/' + p else: if do_splitdrive: drive, p = _my_splitdrive(p) @@ -1285,9 +1326,9 @@ class FS(LocalFS): directory = self._cwd if p in ('', '.'): - p = directory.labspath + p = directory.get_labspath() else: - p = directory.labspath + '/' + p + p = directory.get_labspath() + '/' + p if drive: root = self.get_root(drive) @@ -1412,8 +1453,9 @@ class FS(LocalFS): return cwd.glob(pathname, ondisk, source, strings) class DirNodeInfo(SCons.Node.NodeInfoBase): + __slots__ = () # This should get reset by the FS initialization. - current_version_id = 1 + current_version_id = 2 fs = None @@ -1425,11 +1467,12 @@ class DirNodeInfo(SCons.Node.NodeInfoBase): if drive: root = self.fs.get_root(drive) if not os.path.isabs(s): - s = top.labspath + '/' + s + s = top.get_labspath() + '/' + s return root._lookup_abs(s, Entry) class DirBuildInfo(SCons.Node.BuildInfoBase): - current_version_id = 1 + __slots__ = () + current_version_id = 2 glob_magic_check = re.compile('[*?[]') @@ -1440,7 +1483,22 @@ class Dir(Base): """A class for directories in a file system. """ - memoizer_counters = [] + __slots__ = ['scanner_paths', + 'cachedir_csig', + 'cachesig', + 'repositories', + 'srcdir', + 'entries', + 'searched', + '_sconsign', + 'variant_dirs', + 'root', + 'dirname', + 'on_disk_entries', + 'sccs_dir', + 'rcs_dir', + 'released_target_info', + 'contentsig'] NodeInfo = DirNodeInfo BuildInfo = DirBuildInfo @@ -1470,6 +1528,22 @@ class Dir(Base): self._sconsign = None self.variant_dirs = [] self.root = self.dir.root + self.changed_since_last_build = 3 + self._func_sconsign = 1 + self._func_exists = 2 + self._func_get_contents = 2 + + self.abspath = SCons.Util.silent_intern(self.dir.entry_abspath(self.name)) + self.labspath = SCons.Util.silent_intern(self.dir.entry_labspath(self.name)) + if self.dir.path == '.': + self.path = SCons.Util.silent_intern(self.name) + else: + self.path = SCons.Util.silent_intern(self.dir.entry_path(self.name)) + if self.dir.tpath == '.': + self.tpath = SCons.Util.silent_intern(self.name) + else: + self.tpath = SCons.Util.silent_intern(self.dir.entry_tpath(self.name)) + self.path_elements = self.dir.path_elements + [self] # For directories, we make a difference between the directory # 'name' and the directory 'dirname'. The 'name' attribute is @@ -1562,8 +1636,7 @@ class Dir(Base): return self.srcdir.get_all_rdirs() + self.repositories return self.repositories - memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs')) - + @SCons.Memoize.CountMethodCall def get_all_rdirs(self): try: return list(self._memo['get_all_rdirs']) @@ -1598,8 +1671,7 @@ class Dir(Base): def _rel_path_key(self, other): return str(other) - memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key)) - + @SCons.Memoize.CountDictCall(_rel_path_key) def rel_path(self, other): """Return a path to "other" relative to this directory. """ @@ -1753,10 +1825,7 @@ class Dir(Base): def get_contents(self): """Return content signatures and names of all our children separated by new-lines. Ensure that the nodes are sorted.""" - contents = [] - for node in sorted(self.children(), key=lambda t: t.name): - contents.append('%s %s\n' % (node.get_csig(), node.name)) - return ''.join(contents) + return SCons.Node._get_contents_map[self._func_get_contents](self) def get_csig(self): """Compute the content signature for Directory nodes. In @@ -1770,8 +1839,6 @@ class Dir(Base): def do_duplicate(self, src): pass - changed_since_last_build = SCons.Node.Node.state_has_changed - def is_up_to_date(self): """If any child is not up-to-date, then this directory isn't, either.""" @@ -1795,12 +1862,8 @@ class Dir(Base): return self def sconsign(self): - """Return the .sconsign file info for this directory, - creating it first if necessary.""" - if not self._sconsign: - import SCons.SConsign - self._sconsign = SCons.SConsign.ForDirectory(self) - return self._sconsign + """Return the .sconsign file info for this directory. """ + return _sconsign_map[self._func_sconsign](self) def srcnode(self): """Dir has a special need for srcnode()...if we @@ -1817,6 +1880,23 @@ class Dir(Base): stamp = kid.get_timestamp() return stamp + def get_abspath(self): + """Get the absolute path of the file.""" + return self.abspath + + def get_labspath(self): + """Get the absolute path of the file.""" + return self.labspath + + def get_internal_path(self): + return self.path + + def get_tpath(self): + return self.tpath + + def get_path_elements(self): + return self.path_elements + def entry_abspath(self, name): return self.abspath + OS_SEP + name @@ -1887,8 +1967,7 @@ class Dir(Base): break return rentry_exists - memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list')) - + @SCons.Memoize.CountMethodCall def srcdir_list(self): try: return self._memo['srcdir_list'] @@ -1929,8 +2008,7 @@ class Dir(Base): def _srcdir_find_file_key(self, filename): return filename - memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key)) - + @SCons.Memoize.CountDictCall(_srcdir_find_file_key) def srcdir_find_file(self, filename): try: memo_dict = self._memo['srcdir_find_file'] @@ -2066,8 +2144,9 @@ class Dir(Base): if strings: r = [os.path.join(str(dir), x) for x in r] result.extend(r) - return sorted(result, key=lambda a: str(a)) + return sorted(result, key=lambda a: str(a)) + def _glob1(self, pattern, ondisk=True, source=False, strings=False): """ Globs for and returns a list of entry names matching a single @@ -2146,18 +2225,12 @@ class RootDir(Dir): add a separator when creating the path names of entries within this directory. """ + + __slots__ = ['_lookupDict'] + def __init__(self, drive, fs): if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.RootDir') - # We're going to be our own parent directory (".." entry and .dir - # attribute) so we have to set up some values so Base.__init__() - # won't gag won't it calls some of our methods. - self.abspath = '' - self.labspath = '' - self.path = '' - self.tpath = '' - self.path_elements = [] - self.duplicate = 0 - self.root = self + SCons.Node.Node.__init__(self) # Handle all the types of drives: if drive == '': @@ -2173,7 +2246,16 @@ class RootDir(Dir): name = drive dirname = drive + OS_SEP - Base.__init__(self, name, self, fs) + #: Filename with extension as it was specified when the object was + #: created; to obtain filesystem path, use Python str() function + self.name = SCons.Util.silent_intern(name) + self.fs = fs #: Reference to parent Node.FS object + + self.path_elements = [self] + self.dir = self + self._func_rexists = 2 + self._func_target_from_source = 1 + self.store_info = 1 # Now set our paths to what we really want them to be. The # name should already contain any necessary separators, such @@ -2184,22 +2266,65 @@ class RootDir(Dir): self.labspath = '' self.path = dirname self.tpath = dirname - self._morph() - - # Must be reset after Dir._morph() is invoked... self.dirname = dirname + self._morph() + + self.duplicate = 0 self._lookupDict = {} self._lookupDict[''] = self self._lookupDict['/'] = self - + self.root = self # The // entry is necessary because os.path.normpath() # preserves double slashes at the beginning of a path on Posix # platforms. if not has_unc: self._lookupDict['//'] = self + def _morph(self): + """Turn a file system Node (either a freshly initialized directory + object or a separate Entry object) into a proper directory object. + + Set up this directory's entries and hook it into the file + system tree. Specify that directories (this Node) don't use + signatures for calculating whether they're current. + """ + + self.repositories = [] + self.srcdir = None + + self.entries = {} + self.entries['.'] = self + self.entries['..'] = self.dir + self.cwd = self + self.searched = 0 + self._sconsign = None + self.variant_dirs = [] + self.changed_since_last_build = 3 + self._func_sconsign = 1 + self._func_exists = 2 + self._func_get_contents = 2 + + # Don't just reset the executor, replace its action list, + # because it might have some pre-or post-actions that need to + # be preserved. + # + # But don't reset the executor if there is a non-null executor + # attached already. The existing executor might have other + # targets, in which case replacing the action list with a + # Mkdir action is a big mistake. + if not hasattr(self, 'executor'): + self.builder = get_MkdirBuilder() + self.get_executor().set_action_list(self.builder.action) + else: + # Prepend MkdirBuilder action to existing action list + l = self.get_executor().action_list + a = get_MkdirBuilder().action + l.insert(0, a) + self.get_executor().set_action_list(l) + + def must_be_same(self, klass): if klass is Dir: return @@ -2278,7 +2403,8 @@ class RootDir(Dir): return _null class FileNodeInfo(SCons.Node.NodeInfoBase): - current_version_id = 1 + __slots__ = ('csig', 'timestamp', 'size') + current_version_id = 2 field_list = ['csig', 'timestamp', 'size'] @@ -2293,11 +2419,43 @@ class FileNodeInfo(SCons.Node.NodeInfoBase): if drive: root = self.fs.get_root(drive) if not os.path.isabs(s): - s = top.labspath + '/' + s + s = top.get_labspath() + '/' + s return root._lookup_abs(s, Entry) + def __getstate__(self): + """ + Return all fields that shall be pickled. Walk the slots in the class + hierarchy and add those to the state dictionary. If a '__dict__' slot is + available, copy all entries to the dictionary. Also include the version + id, which is fixed for all instances of a class. + """ + state = getattr(self, '__dict__', {}).copy() + for obj in type(self).mro(): + for name in getattr(obj,'__slots__',()): + if hasattr(self, name): + state[name] = getattr(self, name) + + state['_version_id'] = self.current_version_id + try: + del state['__weakref__'] + except KeyError: + pass + + return state + + def __setstate__(self, state): + """ + Restore the attributes from a pickled state. + """ + # TODO check or discard version + del state['_version_id'] + for key, value in state.items(): + if key not in ('__weakref__',): + setattr(self, key, value) + class FileBuildInfo(SCons.Node.BuildInfoBase): - current_version_id = 1 + __slots__ = () + current_version_id = 2 def convert_to_sconsign(self): """ @@ -2312,7 +2470,7 @@ class FileBuildInfo(SCons.Node.BuildInfoBase): else: def node_to_str(n): try: - s = n.path + s = n.get_internal_path() except AttributeError: s = str(n) else: @@ -2353,6 +2511,8 @@ class FileBuildInfo(SCons.Node.BuildInfoBase): nodeinfos = getattr(self, sattr) except AttributeError: continue + if strings is None or nodeinfos is None: + continue nodes = [] for s, ni in zip(strings, nodeinfos): if not isinstance(s, SCons.Node.Node): @@ -2366,6 +2526,8 @@ class FileBuildInfo(SCons.Node.BuildInfoBase): for bkid, bkidsig in zip(bkids, bkidsigs): result.append(str(bkid) + ': ' + ' '.join(bkidsig.format(names=names))) + if not hasattr(self,'bact'): + self.bact = "none" result.append('%s [%s]' % (self.bactsig, self.bact)) return '\n'.join(result) @@ -2373,7 +2535,22 @@ class File(Base): """A class for files in a file system. """ - memoizer_counters = [] + __slots__ = ['scanner_paths', + 'cachedir_csig', + 'cachesig', + 'repositories', + 'srcdir', + 'entries', + 'searched', + '_sconsign', + 'variant_dirs', + 'root', + 'dirname', + 'on_disk_entries', + 'sccs_dir', + 'rcs_dir', + 'released_target_info', + 'contentsig'] NodeInfo = FileNodeInfo BuildInfo = FileBuildInfo @@ -2424,6 +2601,14 @@ class File(Base): if not hasattr(self, 'released_target_info'): self.released_target_info = False + self.store_info = 1 + self._func_exists = 4 + self._func_get_contents = 3 + + # Initialize this Node's decider function to decide_source() because + # every file is a source file until it has a Builder attached... + self.changed_since_last_build = 4 + # If there was already a Builder set on this entry, then # we need to make sure we call the target-decider function, # not the source-decider. Reaching in and doing this by hand @@ -2435,22 +2620,13 @@ class File(Base): # not clear right now how to fix that, stick with what works # until it becomes clear... if self.has_builder(): - self.changed_since_last_build = self.decide_target + self.changed_since_last_build = 5 def scanner_key(self): return self.get_suffix() def get_contents(self): - if not self.rexists(): - return '' - fname = self.rfile().abspath - try: - contents = open(fname, "rb").read() - except EnvironmentError, e: - if not e.filename: - e.filename = fname - raise - return contents + return SCons.Node._get_contents_map[self._func_get_contents](self) # This attempts to figure out what the encoding of the text is # based upon the BOM bytes, and then decodes the contents so that @@ -2477,7 +2653,7 @@ class File(Base): """ if not self.rexists(): return SCons.Util.MD5signature('') - fname = self.rfile().abspath + fname = self.rfile().get_abspath() try: cs = SCons.Util.MD5filesignature(fname, chunksize=SCons.Node.FS.File.md5_chunksize*1024) @@ -2487,9 +2663,7 @@ class File(Base): raise return cs - - memoizer_counters.append(SCons.Memoize.CountValue('get_size')) - + @SCons.Memoize.CountMethodCall def get_size(self): try: return self._memo['get_size'] @@ -2505,8 +2679,7 @@ class File(Base): return size - memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp')) - + @SCons.Memoize.CountMethodCall def get_timestamp(self): try: return self._memo['get_timestamp'] @@ -2522,14 +2695,6 @@ class File(Base): return timestamp - def store_info(self): - # Merge our build information into the already-stored entry. - # This accomodates "chained builds" where a file that's a target - # in one build (SConstruct file) is a source in a different build. - # See test/chained-build.py for the use case. - if do_store_info: - self.dir.sconsign().store_info(self.name, self) - convert_copy_attrs = [ 'bsources', 'bimplicit', @@ -2642,8 +2807,7 @@ class File(Base): delattr(old_entry, attr) return new_entry - memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info')) - + @SCons.Memoize.CountMethodCall def get_stored_info(self): try: return self._memo['get_stored_info'] @@ -2683,8 +2847,7 @@ class File(Base): def _get_found_includes_key(self, env, scanner, path): return (id(env), id(scanner), path) - memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key)) - + @SCons.Memoize.CountDictCall(_get_found_includes_key) def get_found_includes(self, env, scanner, path): """Return the included implicit dependencies in this file. Cache results so we only scan the file once per path @@ -2768,9 +2931,9 @@ class File(Base): # any build information that's stored in the .sconsign file # into our binfo object so it doesn't get lost. old = self.get_stored_info() - self.get_binfo().__dict__.update(old.binfo.__dict__) + self.get_binfo().merge(old.binfo) - self.store_info() + SCons.Node.store_info_map[self.store_info](self) def release_target_info(self): """Called just after this node has been marked @@ -2870,7 +3033,7 @@ class File(Base): def _rmv_existing(self): self.clear_memoized_values() - if print_duplicate: + if SCons.Node.print_duplicate: print "dup: removing existing target %s"%self e = Unlink(self, [], None) if isinstance(e, SCons.Errors.BuildError): @@ -2906,18 +3069,18 @@ class File(Base): def remove(self): """Remove this file.""" if self.exists() or self.islink(): - self.fs.unlink(self.path) + self.fs.unlink(self.get_internal_path()) return 1 return None def do_duplicate(self, src): self._createDir() - if print_duplicate: + if SCons.Node.print_duplicate: print "dup: relinking variant '%s' from '%s'"%(self, src) Unlink(self, None, None) e = Link(self, src, None) if isinstance(e, SCons.Errors.BuildError): - desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr) + desc = "Cannot duplicate `%s' in `%s': %s." % (src.get_internal_path(), self.dir.path, e.errstr) raise SCons.Errors.StopError(desc) self.linked = 1 # The Link() action may or may not have actually @@ -2926,36 +3089,14 @@ class File(Base): # _rexists attributes so they can be reevaluated. self.clear() - memoizer_counters.append(SCons.Memoize.CountValue('exists')) - + @SCons.Memoize.CountMethodCall def exists(self): try: return self._memo['exists'] except KeyError: pass - # Duplicate from source path if we are set up to do this. - if self.duplicate and not self.is_derived() and not self.linked: - src = self.srcnode() - if src is not self: - # At this point, src is meant to be copied in a variant directory. - src = src.rfile() - if src.abspath != self.abspath: - if src.exists(): - self.do_duplicate(src) - # Can't return 1 here because the duplication might - # not actually occur if the -n option is being used. - else: - # The source file does not exist. Make sure no old - # copy remains in the variant directory. - if print_duplicate: - print "dup: no src for %s, unlinking old variant copy"%self - if Base.exists(self) or self.islink(): - self.fs.unlink(self.path) - # Return None explicitly because the Base.exists() call - # above will have cached its value if the file existed. - self._memo['exists'] = None - return None - result = Base.exists(self) + + result = SCons.Node._exists_map[self._func_exists](self) self._memo['exists'] = result return result @@ -3032,7 +3173,7 @@ class File(Base): def builder_set(self, builder): SCons.Node.Node.builder_set(self, builder) - self.changed_since_last_build = self.decide_target + self.changed_since_last_build = 5 def built(self): """Called just after this File node is successfully built. @@ -3049,7 +3190,7 @@ class File(Base): if (not SCons.Node.interactive and not hasattr(self.attributes, 'keep_targetinfo')): # Ensure that the build infos get computed and cached... - self.store_info() + SCons.Node.store_info_map[self.store_info](self) # ... then release some more variables. self._specific_sources = False self.labspath = None @@ -3111,16 +3252,6 @@ class File(Base): except AttributeError: return 1 - def decide_source(self, target, prev_ni): - return target.get_build_env().decide_source(self, target, prev_ni) - - def decide_target(self, target, prev_ni): - return target.get_build_env().decide_target(self, target, prev_ni) - - # Initialize this Node's decider function to decide_source() because - # every file is a source file until it has a Builder attached... - changed_since_last_build = decide_source - def is_up_to_date(self): T = 0 if T: Trace('is_up_to_date(%s):' % self) @@ -3138,7 +3269,7 @@ class File(Base): e = LocalCopy(self, r, None) if isinstance(e, SCons.Errors.BuildError): raise - self.store_info() + SCons.Node.store_info_map[self.store_info](self) if T: Trace(' 1\n') return 1 self.changed() @@ -3149,8 +3280,7 @@ class File(Base): if T: Trace(' self.exists(): %s\n' % r) return not r - memoizer_counters.append(SCons.Memoize.CountValue('rfile')) - + @SCons.Memoize.CountMethodCall def rfile(self): try: return self._memo['rfile'] @@ -3252,7 +3382,7 @@ class File(Base): # Append this node's signature... sigs.append(self.get_contents_sig()) # ...and it's path - sigs.append(self.path) + sigs.append(self.get_internal_path()) # Merge this all into a single signature result = self.cachesig = SCons.Util.MD5collect(sigs) return result @@ -3268,10 +3398,6 @@ def get_default_fs(): class FileFinder(object): """ """ - if SCons.Memoize.use_memoizer: - __metaclass__ = SCons.Memoize.Memoized_Metaclass - - memoizer_counters = [] def __init__(self): self._memo = {} @@ -3314,8 +3440,7 @@ class FileFinder(object): def _find_file_key(self, filename, paths, verbose=None): return (filename, paths) - memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key)) - + @SCons.Memoize.CountDictCall(_find_file_key) def find_file(self, filename, paths, verbose=None): """ find_file(str, [Dir()]) -> [nodes] diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py index 0326717..0c1e71f 100644 --- a/src/engine/SCons/Node/FSTests.py +++ b/src/engine/SCons/Node/FSTests.py @@ -130,34 +130,34 @@ class VariantDirTestCase(unittest.TestCase): fs.VariantDir('build', 'src') f2 = fs.File('build/test2') d1 = fs.Dir('build') - assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path - assert f2.srcnode().path == os.path.normpath('src/test2'), f2.srcnode().path - assert d1.srcnode().path == 'src', d1.srcnode().path + assert f1.srcnode().get_internal_path() == os.path.normpath('src/test1'), f1.srcnode().get_internal_path() + assert f2.srcnode().get_internal_path() == os.path.normpath('src/test2'), f2.srcnode().get_internal_path() + assert d1.srcnode().get_internal_path() == 'src', d1.srcnode().get_internal_path() fs = SCons.Node.FS.FS() f1 = fs.File('build/test1') fs.VariantDir('build', '.') f2 = fs.File('build/test2') d1 = fs.Dir('build') - assert f1.srcnode().path == 'test1', f1.srcnode().path - assert f2.srcnode().path == 'test2', f2.srcnode().path - assert d1.srcnode().path == '.', d1.srcnode().path + assert f1.srcnode().get_internal_path() == 'test1', f1.srcnode().get_internal_path() + assert f2.srcnode().get_internal_path() == 'test2', f2.srcnode().get_internal_path() + assert d1.srcnode().get_internal_path() == '.', d1.srcnode().get_internal_path() fs = SCons.Node.FS.FS() fs.VariantDir('build/var1', 'src') fs.VariantDir('build/var2', 'src') f1 = fs.File('build/var1/test1') f2 = fs.File('build/var2/test1') - assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path - assert f2.srcnode().path == os.path.normpath('src/test1'), f2.srcnode().path + assert f1.srcnode().get_internal_path() == os.path.normpath('src/test1'), f1.srcnode().get_internal_path() + assert f2.srcnode().get_internal_path() == os.path.normpath('src/test1'), f2.srcnode().get_internal_path() fs = SCons.Node.FS.FS() fs.VariantDir('../var1', 'src') fs.VariantDir('../var2', 'src') f1 = fs.File('../var1/test1') f2 = fs.File('../var2/test1') - assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path - assert f2.srcnode().path == os.path.normpath('src/test1'), f2.srcnode().path + assert f1.srcnode().get_internal_path() == os.path.normpath('src/test1'), f1.srcnode().get_internal_path() + assert f2.srcnode().get_internal_path() == os.path.normpath('src/test1'), f2.srcnode().get_internal_path() # Set up some files test.subdir('work', ['work', 'src']) @@ -210,8 +210,8 @@ class VariantDirTestCase(unittest.TestCase): f2out_2.builder = 1 fs.Repository(test.workpath('rep1')) - assert f1.srcnode().path == os.path.normpath('src/test.in'),\ - f1.srcnode().path + assert f1.srcnode().get_internal_path() == os.path.normpath('src/test.in'),\ + f1.srcnode().get_internal_path() # str(node) returns source path for duplicate = 0 assert str(f1) == os.path.normpath('src/test.in'), str(f1) # Build path does not exist @@ -221,11 +221,11 @@ class VariantDirTestCase(unittest.TestCase): # And duplicate=0 should also work just like a Repository assert f1.rexists() # rfile() should point to the source path - assert f1.rfile().path == os.path.normpath('src/test.in'),\ - f1.rfile().path + assert f1.rfile().get_internal_path() == os.path.normpath('src/test.in'),\ + f1.rfile().get_internal_path() - assert f2.srcnode().path == os.path.normpath('src/test.in'),\ - f2.srcnode().path + assert f2.srcnode().get_internal_path() == os.path.normpath('src/test.in'),\ + f2.srcnode().get_internal_path() # str(node) returns build path for duplicate = 1 assert str(f2) == os.path.normpath('build/var2/test.in'), str(f2) # Build path exists @@ -239,8 +239,8 @@ class VariantDirTestCase(unittest.TestCase): f3 = fs.File('build/var1/test2.in') f4 = fs.File('build/var2/test2.in') - assert f3.srcnode().path == os.path.normpath('src/test2.in'),\ - f3.srcnode().path + assert f3.srcnode().get_internal_path() == os.path.normpath('src/test2.in'),\ + f3.srcnode().get_internal_path() # str(node) returns source path for duplicate = 0 assert str(f3) == os.path.normpath('src/test2.in'), str(f3) # Build path does not exist @@ -250,11 +250,11 @@ class VariantDirTestCase(unittest.TestCase): # But we do have a file in the Repository assert f3.rexists() # rfile() should point to the source path - assert f3.rfile().path == os.path.normpath(test.workpath('rep1/src/test2.in')),\ - f3.rfile().path + assert f3.rfile().get_internal_path() == os.path.normpath(test.workpath('rep1/src/test2.in')),\ + f3.rfile().get_internal_path() - assert f4.srcnode().path == os.path.normpath('src/test2.in'),\ - f4.srcnode().path + assert f4.srcnode().get_internal_path() == os.path.normpath('src/test2.in'),\ + f4.srcnode().get_internal_path() # str(node) returns build path for duplicate = 1 assert str(f4) == os.path.normpath('build/var2/test2.in'), str(f4) # Build path should exist @@ -264,8 +264,8 @@ class VariantDirTestCase(unittest.TestCase): # should exist in repository, since exists() is true assert f4.rexists() # rfile() should point to ourselves - assert f4.rfile().path == os.path.normpath('build/var2/test2.in'),\ - f4.rfile().path + assert f4.rfile().get_internal_path() == os.path.normpath('build/var2/test2.in'),\ + f4.rfile().get_internal_path() f5 = fs.File('build/var1/test.out') f6 = fs.File('build/var2/test.out') @@ -285,14 +285,14 @@ class VariantDirTestCase(unittest.TestCase): assert not f7.exists() assert f7.rexists() - r = f7.rfile().path + r = f7.rfile().get_internal_path() expect = os.path.normpath(test.workpath('rep1/build/var1/test2.out')) assert r == expect, (repr(r), repr(expect)) assert not f8.exists() assert f8.rexists() - assert f8.rfile().path == os.path.normpath(test.workpath('rep1/build/var2/test2.out')),\ - f8.rfile().path + assert f8.rfile().get_internal_path() == os.path.normpath(test.workpath('rep1/build/var2/test2.out')),\ + f8.rfile().get_internal_path() # Verify the Mkdir and Link actions are called d9 = fs.Dir('build/var2/new_dir') @@ -319,9 +319,9 @@ class VariantDirTestCase(unittest.TestCase): d9.reset_executor() f9.exists() expect = os.path.join('build', 'var2', 'new_dir') - assert dir_made[0].path == expect, dir_made[0].path + assert dir_made[0].get_internal_path() == expect, dir_made[0].get_internal_path() expect = os.path.join('build', 'var2', 'new_dir', 'test9.out') - assert link_made[0].path == expect, link_made[0].path + assert link_made[0].get_internal_path() == expect, link_made[0].get_internal_path() assert f9.linked finally: SCons.Node.FS.Link = save_Link @@ -338,7 +338,7 @@ class VariantDirTestCase(unittest.TestCase): f11 = fs.File('src/file11') t, m = f11.alter_targets() - bdt = [n.path for n in t] + bdt = [n.get_internal_path() for n in t] var1_file11 = os.path.normpath('build/var1/file11') var2_file11 = os.path.normpath('build/var2/file11') assert bdt == [var1_file11, var2_file11], bdt @@ -346,11 +346,11 @@ class VariantDirTestCase(unittest.TestCase): f12 = fs.File('src/file12') f12.builder = 1 bdt, m = f12.alter_targets() - assert bdt == [], [n.path for n in bdt] + assert bdt == [], [n.get_internal_path() for n in bdt] d13 = fs.Dir('src/new_dir') t, m = d13.alter_targets() - bdt = [n.path for n in t] + bdt = [n.get_internal_path() for n in t] var1_new_dir = os.path.normpath('build/var1/new_dir') var2_new_dir = os.path.normpath('build/var2/new_dir') assert bdt == [var1_new_dir, var2_new_dir], bdt @@ -566,13 +566,13 @@ class VariantDirTestCase(unittest.TestCase): f = dir + '/f' fnode = fs.File(dir + '/f') - dp = dnode.srcnode().path + dp = dnode.srcnode().get_internal_path() expect = os.path.normpath(srcnode_map.get(dir, dir)) if dp != expect: print "Dir `%s' srcnode() `%s' != expected `%s'" % (dir, dp, expect) errors = errors + 1 - fp = fnode.srcnode().path + fp = fnode.srcnode().get_internal_path() expect = os.path.normpath(srcnode_map.get(f, f)) if fp != expect: print "File `%s' srcnode() `%s' != expected `%s'" % (f, fp, expect) @@ -584,14 +584,14 @@ class VariantDirTestCase(unittest.TestCase): fnode = fs.File(dir + '/f') t, m = dnode.alter_targets() - tp = t[0].path + tp = t[0].get_internal_path() expect = os.path.normpath(alter_map.get(dir, dir)) if tp != expect: print "Dir `%s' alter_targets() `%s' != expected `%s'" % (dir, tp, expect) errors = errors + 1 t, m = fnode.alter_targets() - tp = t[0].path + tp = t[0].get_internal_path() expect = os.path.normpath(alter_map.get(f, f)) if tp != expect: print "File `%s' alter_targets() `%s' != expected `%s'" % (f, tp, expect) @@ -698,19 +698,19 @@ class DirNodeInfoTestCase(_tempdirTestCase): def test___init__(self): """Test DirNodeInfo initialization""" ddd = self.fs.Dir('ddd') - ni = SCons.Node.FS.DirNodeInfo(ddd) + ni = SCons.Node.FS.DirNodeInfo() class DirBuildInfoTestCase(_tempdirTestCase): def test___init__(self): """Test DirBuildInfo initialization""" ddd = self.fs.Dir('ddd') - bi = SCons.Node.FS.DirBuildInfo(ddd) + bi = SCons.Node.FS.DirBuildInfo() class FileNodeInfoTestCase(_tempdirTestCase): def test___init__(self): """Test FileNodeInfo initialization""" fff = self.fs.File('fff') - ni = SCons.Node.FS.FileNodeInfo(fff) + ni = SCons.Node.FS.FileNodeInfo() assert isinstance(ni, SCons.Node.FS.FileNodeInfo) def test_update(self): @@ -718,7 +718,7 @@ class FileNodeInfoTestCase(_tempdirTestCase): test = self.test fff = self.fs.File('fff') - ni = SCons.Node.FS.FileNodeInfo(fff) + ni = SCons.Node.FS.FileNodeInfo() test.write('fff', "fff\n") @@ -765,37 +765,41 @@ class FileBuildInfoTestCase(_tempdirTestCase): def test___init__(self): """Test File.BuildInfo initialization""" fff = self.fs.File('fff') - bi = SCons.Node.FS.FileBuildInfo(fff) + bi = SCons.Node.FS.FileBuildInfo() assert bi, bi def test_convert_to_sconsign(self): """Test converting to .sconsign file format""" fff = self.fs.File('fff') - bi = SCons.Node.FS.FileBuildInfo(fff) + bi = SCons.Node.FS.FileBuildInfo() assert hasattr(bi, 'convert_to_sconsign') def test_convert_from_sconsign(self): """Test converting from .sconsign file format""" fff = self.fs.File('fff') - bi = SCons.Node.FS.FileBuildInfo(fff) + bi = SCons.Node.FS.FileBuildInfo() assert hasattr(bi, 'convert_from_sconsign') def test_prepare_dependencies(self): """Test that we have a prepare_dependencies() method""" fff = self.fs.File('fff') - bi = SCons.Node.FS.FileBuildInfo(fff) + bi = SCons.Node.FS.FileBuildInfo() bi.prepare_dependencies() def test_format(self): """Test the format() method""" f1 = self.fs.File('f1') - bi1 = SCons.Node.FS.FileBuildInfo(f1) + bi1 = SCons.Node.FS.FileBuildInfo() - s1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n1')) + self.fs.File('n1') + self.fs.File('n2') + self.fs.File('n3') + + s1sig = SCons.Node.FS.FileNodeInfo() s1sig.csig = 1 - d1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n2')) + d1sig = SCons.Node.FS.FileNodeInfo() d1sig.timestamp = 2 - i1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n3')) + i1sig = SCons.Node.FS.FileNodeInfo() i1sig.size = 3 bi1.bsources = [self.fs.File('s1')] @@ -953,7 +957,7 @@ class FSTestCase(_tempdirTestCase): assert isinstance(f1, SCons.Node.FS.File) d1_f1 = os.path.join('d1', 'f1') - assert f1.path == d1_f1, "f1.path %s != %s" % (f1.path, d1_f1) + assert f1.get_internal_path() == d1_f1, "f1.path %s != %s" % (f1.get_internal_path(), d1_f1) assert str(f1) == d1_f1, "str(f1) %s != %s" % (str(f1), d1_f1) x1 = d1.File('x1') @@ -1045,16 +1049,16 @@ class FSTestCase(_tempdirTestCase): name = os.sep if dir.up() is None: - dir_up_path = dir.path + dir_up_path = dir.get_internal_path() else: - dir_up_path = dir.up().path + dir_up_path = dir.up().get_internal_path() assert dir.name == name, \ "dir.name %s != expected name %s" % \ (dir.name, name) - assert dir.path == path, \ + assert dir.get_internal_path() == path, \ "dir.path %s != expected path %s" % \ - (dir.path, path) + (dir.get_internal_path(), path) assert str(dir) == path, \ "str(dir) %s != expected path %s" % \ (str(dir), path) @@ -1136,9 +1140,9 @@ class FSTestCase(_tempdirTestCase): # Test for a bug in 0.04 that did not like looking up # dirs with a trailing slash on Windows. d=fs.Dir('./') - assert d.path == '.', d.abspath + assert d.get_internal_path() == '.', d.get_abspath() d=fs.Dir('foo/') - assert d.path == 'foo', d.abspath + assert d.get_internal_path() == 'foo', d.get_abspath() # Test for sub-classing of node building. global built_it @@ -1167,50 +1171,50 @@ class FSTestCase(_tempdirTestCase): e1 = fs.Entry("d1") assert e1.__class__.__name__ == 'Dir' - match(e1.path, "d1") - match(e1.dir.path, ".") + match(e1.get_internal_path(), "d1") + match(e1.dir.get_internal_path(), ".") e2 = fs.Entry("d1/f1") assert e2.__class__.__name__ == 'File' - match(e2.path, "d1/f1") - match(e2.dir.path, "d1") + match(e2.get_internal_path(), "d1/f1") + match(e2.dir.get_internal_path(), "d1") e3 = fs.Entry("e3") assert e3.__class__.__name__ == 'Entry' - match(e3.path, "e3") - match(e3.dir.path, ".") + match(e3.get_internal_path(), "e3") + match(e3.dir.get_internal_path(), ".") e4 = fs.Entry("d1/e4") assert e4.__class__.__name__ == 'Entry' - match(e4.path, "d1/e4") - match(e4.dir.path, "d1") + match(e4.get_internal_path(), "d1/e4") + match(e4.dir.get_internal_path(), "d1") e5 = fs.Entry("e3/e5") assert e3.__class__.__name__ == 'Dir' - match(e3.path, "e3") - match(e3.dir.path, ".") + match(e3.get_internal_path(), "e3") + match(e3.dir.get_internal_path(), ".") assert e5.__class__.__name__ == 'Entry' - match(e5.path, "e3/e5") - match(e5.dir.path, "e3") + match(e5.get_internal_path(), "e3/e5") + match(e5.dir.get_internal_path(), "e3") e6 = fs.Dir("d1/e4") assert e6 is e4 assert e4.__class__.__name__ == 'Dir' - match(e4.path, "d1/e4") - match(e4.dir.path, "d1") + match(e4.get_internal_path(), "d1/e4") + match(e4.dir.get_internal_path(), "d1") e7 = fs.File("e3/e5") assert e7 is e5 assert e5.__class__.__name__ == 'File' - match(e5.path, "e3/e5") - match(e5.dir.path, "e3") + match(e5.get_internal_path(), "e3/e5") + match(e5.dir.get_internal_path(), "e3") fs.chdir(fs.Dir('subdir')) f11 = fs.File("f11") - match(f11.path, "subdir/f11") + match(f11.get_internal_path(), "subdir/f11") d12 = fs.Dir("d12") e13 = fs.Entry("subdir/e13") - match(e13.path, "subdir/subdir/e13") + match(e13.get_internal_path(), "subdir/subdir/e13") fs.chdir(fs.Dir('..')) # Test scanning @@ -1220,13 +1224,13 @@ class FSTestCase(_tempdirTestCase): f1.builder.target_scanner = Scanner(xyz) f1.scan() - assert f1.implicit[0].path == "xyz" + assert f1.implicit[0].get_internal_path() == "xyz" f1.implicit = [] f1.scan() assert f1.implicit == [] f1.implicit = None f1.scan() - assert f1.implicit[0].path == "xyz" + assert f1.implicit[0].get_internal_path() == "xyz" # Test underlying scanning functionality in get_found_includes() env = Environment() @@ -1284,9 +1288,9 @@ class FSTestCase(_tempdirTestCase): fs.chdir(fs.Dir('subdir')) # The cwd's path is always "." assert str(fs.getcwd()) == ".", str(fs.getcwd()) - assert fs.getcwd().path == 'subdir', fs.getcwd().path + assert fs.getcwd().get_internal_path() == 'subdir', fs.getcwd().get_internal_path() fs.chdir(fs.Dir('../..')) - assert fs.getcwd().path == test.workdir, fs.getcwd().path + assert fs.getcwd().get_internal_path() == test.workdir, fs.getcwd().get_internal_path() f1 = fs.File(test.workpath("do_i_exist")) assert not f1.exists() @@ -1589,15 +1593,15 @@ class FSTestCase(_tempdirTestCase): assert dir.name == name, \ "dir.name %s != expected name %s" % \ (dir.name, name) - assert dir.path == path, \ + assert dir.get_internal_path() == path, \ "dir.path %s != expected path %s" % \ - (dir.path, path) + (dir.get_internal_path(), path) assert str(dir) == path, \ "str(dir) %s != expected path %s" % \ (str(dir), path) - assert dir.up().path == up_path, \ + assert dir.up().get_internal_path() == up_path, \ "dir.up().path %s != expected parent path %s" % \ - (dir.up().path, up_path) + (dir.up().get_internal_path(), up_path) save_os_path = os.path save_os_sep = os.sep @@ -1691,22 +1695,22 @@ class FSTestCase(_tempdirTestCase): name = path.split(os.sep)[-1] if dir.up() is None: - dir_up_path = dir.path + dir_up_path = dir.get_internal_path() else: - dir_up_path = dir.up().path + dir_up_path = dir.up().get_internal_path() assert dir.name == name, \ "dir.name %s != expected name %s" % \ (dir.name, name) - assert dir.path == path, \ + assert dir.get_internal_path() == path, \ "dir.path %s != expected path %s" % \ - (dir.path, path) + (dir.get_internal_path(), path) assert str(dir) == path, \ "str(dir) %s != expected path %s" % \ (str(dir), path) assert dir_up_path == up_path, \ "dir.up().path %s != expected parent path %s" % \ - (dir.up().path, up_path) + (dir.up().get_internal_path(), up_path) save_os_path = os.path save_os_sep = os.sep @@ -1792,7 +1796,7 @@ class FSTestCase(_tempdirTestCase): d1 = fs.Dir('d1') d2 = d1.Dir('d2') - dirs = os.path.normpath(d2.abspath).split(os.sep) + dirs = os.path.normpath(d2.get_abspath()).split(os.sep) above_path = os.path.join(*['..']*len(dirs) + ['above']) above = d2.Dir(above_path) @@ -1831,9 +1835,9 @@ class FSTestCase(_tempdirTestCase): fs = self.fs if sys.platform not in ('win32',): return - p = fs.Dir(r"\\computername\sharename").abspath + p = fs.Dir(r"\\computername\sharename").get_abspath() assert p == r"\\computername\sharename", p - p = fs.Dir(r"\\\computername\sharename").abspath + p = fs.Dir(r"\\\computername\sharename").get_abspath() assert p == r"\\computername\sharename", p def test_rel_path(self): @@ -1980,7 +1984,7 @@ class DirTestCase(_tempdirTestCase): fs.Dir(os.path.join('ddd', 'd1', 'f4')) fs.Dir(os.path.join('ddd', 'd1', 'f5')) dir.scan() - kids = sorted([x.path for x in dir.children(None)]) + kids = sorted([x.get_internal_path() for x in dir.children(None)]) assert kids == [os.path.join('ddd', 'd1'), os.path.join('ddd', 'f1'), os.path.join('ddd', 'f2'), @@ -2024,12 +2028,12 @@ class DirTestCase(_tempdirTestCase): fs.File(os.path.join('ddd', 'f1')) dir.scan() - kids = sorted([x.path for x in dir.children()]) + kids = sorted([x.get_internal_path() for x in dir.children()]) assert kids == [os.path.join('ddd', 'f1')], kids fs.File(os.path.join('ddd', 'f2')) dir.scan() - kids = sorted([x.path for x in dir.children()]) + kids = sorted([x.get_internal_path() for x in dir.children()]) assert kids == [os.path.join('ddd', 'f1'), os.path.join('ddd', 'f2')], kids @@ -2171,8 +2175,12 @@ class DirTestCase(_tempdirTestCase): """ test = self.test - return_true = lambda: 1 + def return_true(node): + return 1 + SCons.Node._is_derived_map[2] = return_true + SCons.Node._exists_map[5] = return_true + test.subdir('src0') test.write(['src0', 'on-disk-f1'], "src0/on-disk-f1\n") test.write(['src0', 'on-disk-f2'], "src0/on-disk-f2\n") @@ -2184,14 +2192,14 @@ class DirTestCase(_tempdirTestCase): self.fs.VariantDir(bld0, src0, duplicate=0) derived_f = src0.File('derived-f') - derived_f.is_derived = return_true + derived_f._func_is_derived = 2 exists_f = src0.File('exists-f') - exists_f.exists = return_true + exists_f._func_exists = 5 derived_e = src0.Entry('derived-e') - derived_e.is_derived = return_true + derived_e._func_is_derived = 2 exists_e = src0.Entry('exists-e') - exists_e.exists = return_true + exists_e._func_exists = 5 def check(result, expect): result = list(map(str, result)) @@ -2245,14 +2253,14 @@ class DirTestCase(_tempdirTestCase): self.fs.VariantDir(bld1, src1, duplicate=1) derived_f = src1.File('derived-f') - derived_f.is_derived = return_true + derived_f._func_is_derived = 2 exists_f = src1.File('exists-f') - exists_f.exists = return_true + exists_f._func_exists = 5 derived_e = src1.Entry('derived-e') - derived_e.is_derived = return_true + derived_e._func_is_derived = 2 exists_e = src1.Entry('exists-e') - exists_e.exists = return_true + exists_e._func_exists = 5 # First check from the source directory. n = src1.srcdir_find_file('does_not_exist') @@ -2432,7 +2440,7 @@ class FileTestCase(_tempdirTestCase): build_f1 = fs.File('build/f1') assert build_f1.exists(), "%s did not realize that %s exists" % (build_f1, src_f1) - assert os.path.exists(build_f1.abspath), "%s did not get duplicated on disk" % build_f1.abspath + assert os.path.exists(build_f1.get_abspath()), "%s did not get duplicated on disk" % build_f1.get_abspath() test.unlink(['src', 'f1']) src_f1.clear() # so the next exists() call will look on disk again @@ -2441,7 +2449,7 @@ class FileTestCase(_tempdirTestCase): build_f1.clear() build_f1.linked = None assert not build_f1.exists(), "%s did not realize that %s disappeared" % (build_f1, src_f1) - assert not os.path.exists(build_f1.abspath), "%s did not get removed after %s was removed" % (build_f1, src_f1) + assert not os.path.exists(build_f1.get_abspath()), "%s did not get removed after %s was removed" % (build_f1, src_f1) @@ -2535,7 +2543,7 @@ class GlobTestCase(_tempdirTestCase): for input, string_expect, node_expect in cases: r = self.fs.Glob(input, **kwargs) if node_expect: - r = sorted(r, key=lambda a: a.path) + r = sorted(r, key=lambda a: a.get_internal_path()) result = [] for n in node_expect: if isinstance(n, str): @@ -2906,8 +2914,14 @@ class RepositoryTestCase(_tempdirTestCase): def test_rdir(self): """Test the Dir.rdir() method""" - return_true = lambda: 1 - return_false = lambda: 0 + def return_true(obj): + return 1 + def return_false(obj): + return 0 + SCons.Node._exists_map[5] = return_true + SCons.Node._exists_map[6] = return_false + SCons.Node._is_derived_map[2] = return_true + SCons.Node._is_derived_map[3] = return_false d1 = self.fs.Dir('d1') d2 = self.fs.Dir('d2') @@ -2931,19 +2945,19 @@ class RepositoryTestCase(_tempdirTestCase): assert r == os.path.join(self.rep3, 'd3'), r e1 = self.fs.Dir('e1') - e1.exists = return_false + e1._func_exists = 6 e2 = self.fs.Dir('e2') - e2.exists = return_false + e2._func_exists = 6 # Make sure we match entries in repositories, # regardless of whether they're derived or not. re1 = self.fs.Entry(os.path.join(self.rep1, 'e1')) - re1.exists = return_true - re1.is_derived = return_true + re1._func_exists = 5 + re1._func_is_derived = 2 re2 = self.fs.Entry(os.path.join(self.rep2, 'e2')) - re2.exists = return_true - re2.is_derived = return_false + re2._func_exists = 5 + re2._func_is_derived = 3 r = e1.rdir() assert r is re1, r @@ -2953,8 +2967,14 @@ class RepositoryTestCase(_tempdirTestCase): def test_rfile(self): """Test the File.rfile() method""" - return_true = lambda: 1 - return_false = lambda: 0 + def return_true(obj): + return 1 + def return_false(obj): + return 0 + SCons.Node._exists_map[5] = return_true + SCons.Node._exists_map[6] = return_false + SCons.Node._is_derived_map[2] = return_true + SCons.Node._is_derived_map[3] = return_false f1 = self.fs.File('f1') f2 = self.fs.File('f2') @@ -2978,19 +2998,19 @@ class RepositoryTestCase(_tempdirTestCase): assert r == os.path.join(self.rep3, 'f3'), r e1 = self.fs.File('e1') - e1.exists = return_false + e1._func_exists = 6 e2 = self.fs.File('e2') - e2.exists = return_false + e2._func_exists = 6 # Make sure we match entries in repositories, # regardless of whether they're derived or not. re1 = self.fs.Entry(os.path.join(self.rep1, 'e1')) - re1.exists = return_true - re1.is_derived = return_true + re1._func_exists = 5 + re1._func_is_derived = 2 re2 = self.fs.Entry(os.path.join(self.rep2, 'e2')) - re2.exists = return_true - re2.is_derived = return_false + re2._func_exists = 5 + re2._func_is_derived = 3 r = e1.rfile() assert r is re1, r @@ -3244,9 +3264,13 @@ class stored_infoTestCase(unittest.TestCase): self.xyzzy = 7 def get_entry(self, name): return self.Null() + + def test_sconsign(node): + return MySConsign() f = fs.File('file2', d) - f.dir.sconsign = MySConsign + SCons.Node.FS._sconsign_map[2] = test_sconsign + f.dir._func_sconsign = 2 bi = f.get_stored_info() assert bi.xyzzy == 7, bi @@ -3360,7 +3384,7 @@ class prepareTestCase(unittest.TestCase): xyz.set_state(0) xyz.prepare() - assert dir_made[0].path == "new_dir", dir_made[0] + assert dir_made[0].get_internal_path() == "new_dir", dir_made[0] dir = fs.Dir("dir") dir.prepare() @@ -3373,7 +3397,7 @@ class SConstruct_dirTestCase(unittest.TestCase): fs = SCons.Node.FS.FS() fs.set_SConstruct_dir(fs.Dir('xxx')) - assert fs.SConstruct_dir.path == 'xxx' + assert fs.SConstruct_dir.get_internal_path() == 'xxx' @@ -3552,7 +3576,7 @@ class SpecialAttrTestCase(unittest.TestCase): for_sig = f.suffix.for_signature() assert for_sig == 'baz.blat_suffix', for_sig - s = str(f.abspath) + s = str(f.get_abspath()) assert s == test.workpath('work', 'foo', 'bar', 'baz.blat'), s assert f.abspath.is_literal(), f.abspath for_sig = f.abspath.for_signature() diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py index da502b0..a6471b4 100644 --- a/src/engine/SCons/Node/NodeTests.py +++ b/src/engine/SCons/Node/NodeTests.py @@ -184,7 +184,7 @@ class Scanner(object): called = None def __call__(self, node): self.called = 1 - return node.found_includes + return node.GetTag('found_includes') def path(self, env, dir, target=None, source=None): return () def select(self, node): @@ -200,7 +200,7 @@ class MyNode(SCons.Node.Node): def __init__(self, name): SCons.Node.Node.__init__(self) self.name = name - self.found_includes = [] + self.Tag('found_includes', []) def __str__(self): return self.name def get_found_includes(self, env, scanner, target): @@ -224,11 +224,18 @@ class Calculator(object): class NodeInfoBaseTestCase(unittest.TestCase): + # The abstract class NodeInfoBase has not enough default slots to perform + # the merge and format test (arbitrary attributes do not work). Do it with a + # derived class that does provide the slots. def test_merge(self): """Test merging NodeInfoBase attributes""" - ni1 = SCons.Node.NodeInfoBase(SCons.Node.Node()) - ni2 = SCons.Node.NodeInfoBase(SCons.Node.Node()) + + class TestNodeInfo(SCons.Node.NodeInfoBase): + __slots__ = ('a1', 'a2', 'a3') + + ni1 = TestNodeInfo() + ni2 = TestNodeInfo() ni1.a1 = 1 ni1.a2 = 2 @@ -237,27 +244,32 @@ class NodeInfoBaseTestCase(unittest.TestCase): ni2.a3 = 333 ni1.merge(ni2) - expect = {'a1':1, 'a2':222, 'a3':333, '_version_id':1} - assert ni1.__dict__ == expect, ni1.__dict__ + assert ni1.a1 == 1, ni1.a1 + assert ni1.a2 == 222, ni1.a2 + assert ni1.a3 == 333, ni1.a3 def test_update(self): """Test the update() method""" - ni = SCons.Node.NodeInfoBase(SCons.Node.Node()) + ni = SCons.Node.NodeInfoBase() ni.update(SCons.Node.Node()) def test_format(self): """Test the NodeInfoBase.format() method""" - ni1 = SCons.Node.NodeInfoBase(SCons.Node.Node()) + + class TestNodeInfo(SCons.Node.NodeInfoBase): + __slots__ = ('xxx', 'yyy', 'zzz') + + ni1 = TestNodeInfo() ni1.xxx = 'x' ni1.yyy = 'y' ni1.zzz = 'z' f = ni1.format() - assert f == ['1', 'x', 'y', 'z'], f + assert f == ['x', 'y', 'z'], f + + field_list = ['xxx', 'zzz', 'aaa'] - ni1.field_list = ['xxx', 'zzz', 'aaa'] - - f = ni1.format() + f = ni1.format(field_list) assert f == ['x', 'z', 'None'], f @@ -267,26 +279,26 @@ class BuildInfoBaseTestCase(unittest.TestCase): def test___init__(self): """Test BuildInfoBase initialization""" n = SCons.Node.Node() - bi = SCons.Node.BuildInfoBase(n) + bi = SCons.Node.BuildInfoBase() assert bi def test_merge(self): """Test merging BuildInfoBase attributes""" n1 = SCons.Node.Node() - bi1 = SCons.Node.BuildInfoBase(n1) + bi1 = SCons.Node.BuildInfoBase() n2 = SCons.Node.Node() - bi2 = SCons.Node.BuildInfoBase(n2) + bi2 = SCons.Node.BuildInfoBase() - bi1.a1 = 1 - bi1.a2 = 2 + bi1.bsources = 1 + bi1.bdepends = 2 - bi2.a2 = 222 - bi2.a3 = 333 + bi2.bdepends = 222 + bi2.bact = 333 bi1.merge(bi2) - assert bi1.a1 == 1, bi1.a1 - assert bi1.a2 == 222, bi1.a2 - assert bi1.a3 == 333, bi1.a3 + assert bi1.bsources == 1, bi1.bsources + assert bi1.bdepends == 222, bi1.bdepends + assert bi1.bact == 333, bi1.bact class NodeTestCase(unittest.TestCase): @@ -427,6 +439,7 @@ class NodeTestCase(unittest.TestCase): def test_built(self): """Test the built() method""" class SubNodeInfo(SCons.Node.NodeInfoBase): + __slots__ = ('updated',) def update(self, node): self.updated = 1 class SubNode(SCons.Node.Node): @@ -434,7 +447,7 @@ class NodeTestCase(unittest.TestCase): self.cleared = 1 n = SubNode() - n.ninfo = SubNodeInfo(n) + n.ninfo = SubNodeInfo() n.built() assert n.cleared, n.cleared assert n.ninfo.updated, n.ninfo.cleared @@ -568,32 +581,56 @@ class NodeTestCase(unittest.TestCase): def test_get_csig(self): """Test generic content signature calculation """ - node = SCons.Node.Node() - node.get_contents = lambda: 444 - result = node.get_csig() - assert result == '550a141f12de6341fba65b0ad0433500', result + + class TestNodeInfo(SCons.Node.NodeInfoBase): + __slots__ = ('csig',) + try: + SCons.Node.Node.NodeInfo = TestNodeInfo + def my_contents(obj): + return 444 + SCons.Node._get_contents_map[4] = my_contents + node = SCons.Node.Node() + node._func_get_contents = 4 + result = node.get_csig() + assert result == '550a141f12de6341fba65b0ad0433500', result + finally: + SCons.Node.Node.NodeInfo = SCons.Node.NodeInfoBase def test_get_cachedir_csig(self): """Test content signature calculation for CacheDir """ - node = SCons.Node.Node() - node.get_contents = lambda: 555 - result = node.get_cachedir_csig() - assert result == '15de21c670ae7c3f6f3f1f37029303c9', result + class TestNodeInfo(SCons.Node.NodeInfoBase): + __slots__ = ('csig',) + try: + SCons.Node.Node.NodeInfo = TestNodeInfo + def my_contents(obj): + return 555 + SCons.Node._get_contents_map[4] = my_contents + node = SCons.Node.Node() + node._func_get_contents = 4 + result = node.get_cachedir_csig() + assert result == '15de21c670ae7c3f6f3f1f37029303c9', result + finally: + SCons.Node.Node.NodeInfo = SCons.Node.NodeInfoBase def test_get_binfo(self): """Test fetching/creating a build information structure """ + class TestNodeInfo(SCons.Node.NodeInfoBase): + __slots__ = ('csig',) + SCons.Node.Node.NodeInfo = TestNodeInfo node = SCons.Node.Node() - + binfo = node.get_binfo() assert isinstance(binfo, SCons.Node.BuildInfoBase), binfo node = SCons.Node.Node() d = SCons.Node.Node() - d.get_ninfo().csig = 777 + ninfo = d.get_ninfo() + assert isinstance(ninfo, SCons.Node.NodeInfoBase), ninfo i = SCons.Node.Node() - i.get_ninfo().csig = 888 + ninfo = i.get_ninfo() + assert isinstance(ninfo, SCons.Node.NodeInfoBase), ninfo node.depends = [d] node.implicit = [i] @@ -655,7 +692,7 @@ class NodeTestCase(unittest.TestCase): """Test calling the method to store build information """ node = SCons.Node.Node() - node.store_info() + SCons.Node.store_info_map[node.store_info](node) def test_get_stored_info(self): """Test calling the method to fetch stored build information @@ -888,7 +925,7 @@ class NodeTestCase(unittest.TestCase): s = Scanner() d1 = MyNode("d1") d2 = MyNode("d2") - node.found_includes = [d1, d2] + node.Tag('found_includes', [d1, d2]) # Simple return of the found includes deps = node.get_implicit_deps(env, s, target) @@ -898,14 +935,14 @@ class NodeTestCase(unittest.TestCase): e = MyNode("eee") f = MyNode("fff") g = MyNode("ggg") - d1.found_includes = [e, f] - d2.found_includes = [e, f] - f.found_includes = [g] + d1.Tag('found_includes', [e, f]) + d2.Tag('found_includes', [e, f]) + f.Tag('found_includes', [g]) deps = node.get_implicit_deps(env, s, target) assert deps == [d1, d2, e, f, g], list(map(str, deps)) # Recursive scanning eliminates duplicates - e.found_includes = [f] + e.Tag('found_includes', [f]) deps = node.get_implicit_deps(env, s, target) assert deps == [d1, d2, e, f, g], list(map(str, deps)) @@ -994,7 +1031,7 @@ class NodeTestCase(unittest.TestCase): s = Scanner() d = MyNode("ddd") - node.found_includes = [d] + node.Tag('found_includes', [d]) node.builder.target_scanner = s assert node.implicit is None @@ -1207,7 +1244,7 @@ class NodeTestCase(unittest.TestCase): def test_Annotate(self): """Test using an interface-specific Annotate function.""" def my_annotate(node, self=self): - node.annotation = self.node_string + node.Tag('annotation', self.node_string) save_Annotate = SCons.Node.Annotate SCons.Node.Annotate = my_annotate @@ -1215,11 +1252,13 @@ class NodeTestCase(unittest.TestCase): try: self.node_string = '#1' n = SCons.Node.Node() - assert n.annotation == '#1', n.annotation + a = n.GetTag('annotation') + assert a == '#1', a self.node_string = '#2' n = SCons.Node.Node() - assert n.annotation == '#2', n.annotation + a = n.GetTag('annotation') + assert a == '#2', a finally: SCons.Node.Annotate = save_Annotate @@ -1230,7 +1269,7 @@ class NodeTestCase(unittest.TestCase): n.set_state(3) n.binfo = 'xyz' n.includes = 'testincludes' - n.found_include = {'testkey':'testvalue'} + n.Tag('found_includes', {'testkey':'testvalue'}) n.implicit = 'testimplicit' x = MyExecutor() diff --git a/src/engine/SCons/Node/Python.py b/src/engine/SCons/Node/Python.py index 3d8bdaa..f151fc5 100644 --- a/src/engine/SCons/Node/Python.py +++ b/src/engine/SCons/Node/Python.py @@ -32,15 +32,49 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import SCons.Node class ValueNodeInfo(SCons.Node.NodeInfoBase): - current_version_id = 1 + __slots__ = ('csig',) + current_version_id = 2 field_list = ['csig'] def str_to_node(self, s): return Value(s) + def __getstate__(self): + """ + Return all fields that shall be pickled. Walk the slots in the class + hierarchy and add those to the state dictionary. If a '__dict__' slot is + available, copy all entries to the dictionary. Also include the version + id, which is fixed for all instances of a class. + """ + state = getattr(self, '__dict__', {}).copy() + for obj in type(self).mro(): + for name in getattr(obj,'__slots__',()): + if hasattr(self, name): + state[name] = getattr(self, name) + + state['_version_id'] = self.current_version_id + try: + del state['__weakref__'] + except KeyError: + pass + + return state + + def __setstate__(self, state): + """ + Restore the attributes from a pickled state. + """ + # TODO check or discard version + del state['_version_id'] + for key, value in state.items(): + if key not in ('__weakref__',): + setattr(self, key, value) + + class ValueBuildInfo(SCons.Node.BuildInfoBase): - current_version_id = 1 + __slots__ = () + current_version_id = 2 class Value(SCons.Node.Node): """A class for Python variables, typically passed on the command line @@ -53,6 +87,8 @@ class Value(SCons.Node.Node): def __init__(self, value, built_value=None): SCons.Node.Node.__init__(self) self.value = value + self.changed_since_last_build = 6 + self.store_info = 0 if built_value is not None: self.built_value = built_value diff --git a/src/engine/SCons/Node/PythonTests.py b/src/engine/SCons/Node/PythonTests.py index fcdfe77..e2e36bf 100644 --- a/src/engine/SCons/Node/PythonTests.py +++ b/src/engine/SCons/Node/PythonTests.py @@ -104,13 +104,13 @@ class ValueNodeInfoTestCase(unittest.TestCase): def test___init__(self): """Test ValueNodeInfo initialization""" vvv = SCons.Node.Python.Value('vvv') - ni = SCons.Node.Python.ValueNodeInfo(vvv) + ni = SCons.Node.Python.ValueNodeInfo() class ValueBuildInfoTestCase(unittest.TestCase): def test___init__(self): """Test ValueBuildInfo initialization""" vvv = SCons.Node.Python.Value('vvv') - bi = SCons.Node.Python.ValueBuildInfo(vvv) + bi = SCons.Node.Python.ValueBuildInfo() if __name__ == "__main__": suite = unittest.TestSuite() diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index 1f62971..f2d37c2 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -55,6 +55,8 @@ import SCons.Util from SCons.Debug import Trace +print_duplicate = 0 + def classname(obj): return str(obj.__class__).split('.')[-1] @@ -105,6 +107,233 @@ Annotate = do_nothing # clean builds and update runs (see release_target_info). interactive = False +def is_derived_none(node): + raise NotImplementedError + +def is_derived_node(node): + """ + Returns true if this node is derived (i.e. built). + """ + return node.has_builder() or node.side_effect + +_is_derived_map = {0 : is_derived_none, + 1 : is_derived_node} + +def exists_none(node): + raise NotImplementedError + +def exists_always(node): + return 1 + +def exists_base(node): + return node.stat() is not None + +def exists_entry(node): + """Return if the Entry exists. Check the file system to see + what we should turn into first. Assume a file if there's no + directory.""" + node.disambiguate() + return _exists_map[node._func_exists](node) + +def exists_file(node): + # Duplicate from source path if we are set up to do this. + if node.duplicate and not node.is_derived() and not node.linked: + src = node.srcnode() + if src is not node: + # At this point, src is meant to be copied in a variant directory. + src = src.rfile() + if src.get_abspath() != node.get_abspath(): + if src.exists(): + node.do_duplicate(src) + # Can't return 1 here because the duplication might + # not actually occur if the -n option is being used. + else: + # The source file does not exist. Make sure no old + # copy remains in the variant directory. + if print_duplicate: + print "dup: no src for %s, unlinking old variant copy"%self + if exists_base(node) or node.islink(): + node.fs.unlink(node.get_internal_path()) + # Return None explicitly because the Base.exists() call + # above will have cached its value if the file existed. + return None + return exists_base(node) + +_exists_map = {0 : exists_none, + 1 : exists_always, + 2 : exists_base, + 3 : exists_entry, + 4 : exists_file} + + +def rexists_none(node): + raise NotImplementedError + +def rexists_node(node): + return node.exists() + +def rexists_base(node): + return node.rfile().exists() + +_rexists_map = {0 : rexists_none, + 1 : rexists_node, + 2 : rexists_base} + +def get_contents_none(node): + raise NotImplementedError + +def get_contents_entry(node): + """Fetch the contents of the entry. Returns the exact binary + contents of the file.""" + try: + node = node.disambiguate(must_exist=1) + 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 + # like (e.g. in qt.py) don't have to disambiguate by hand + # or catch the exception. + return '' + else: + return _get_contents_map[node._func_get_contents](node) + +def get_contents_dir(node): + """Return content signatures and names of all our children + separated by new-lines. Ensure that the nodes are sorted.""" + contents = [] + for n in sorted(node.children(), key=lambda t: t.name): + contents.append('%s %s\n' % (n.get_csig(), n.name)) + return ''.join(contents) + +def get_contents_file(node): + if not node.rexists(): + return '' + fname = node.rfile().get_abspath() + try: + contents = open(fname, "rb").read() + except EnvironmentError, e: + if not e.filename: + e.filename = fname + raise + return contents + +_get_contents_map = {0 : get_contents_none, + 1 : get_contents_entry, + 2 : get_contents_dir, + 3 : get_contents_file} + +def target_from_source_none(node, prefix, suffix, splitext): + raise NotImplementedError + +def target_from_source_base(node, prefix, suffix, splitext): + return node.dir.Entry(prefix + splitext(node.name)[0] + suffix) + +_target_from_source_map = {0 : target_from_source_none, + 1 : target_from_source_base} + +# +# The new decider subsystem for Nodes +# +# We would set and overwrite the changed_since_last_build function +# before, but for being able to use slots (less memory!) we now have +# a dictionary of the different decider functions. Then in the Node +# subclasses we simply store the index to the decider that should be +# used by it. +# + +# +# First, the single decider functions +# +def changed_since_last_build_node(node, target, prev_ni): + """ + + Must be overridden in a specific subclass to return True if this + Node (a dependency) has changed since the last time it was used + to build the specified target. prev_ni is this Node's state (for + example, its file timestamp, length, maybe content signature) + as of the last time the target was built. + + Note that this method is called through the dependency, not the + target, because a dependency Node must be able to use its own + logic to decide if it changed. For example, File Nodes need to + obey if we're configured to use timestamps, but Python Value Nodes + never use timestamps and always use the content. If this method + were called through the target, then each Node's implementation + of this method would have to have more complicated logic to + handle all the different Node types on which it might depend. + """ + raise NotImplementedError + +def changed_since_last_build_alias(node, target, prev_ni): + cur_csig = node.get_csig() + try: + return cur_csig != prev_ni.csig + except AttributeError: + return 1 + +def changed_since_last_build_entry(node, target, prev_ni): + node.disambiguate() + return _decider_map[node.changed_since_last_build](node, target, prev_ni) + +def changed_since_last_build_state_changed(node, target, prev_ni): + return (node.state != SCons.Node.up_to_date) + +def decide_source(node, target, prev_ni): + return target.get_build_env().decide_source(node, target, prev_ni) + +def decide_target(node, target, prev_ni): + return target.get_build_env().decide_target(node, target, prev_ni) + +def changed_since_last_build_python(node, target, prev_ni): + cur_csig = node.get_csig() + try: + return cur_csig != prev_ni.csig + except AttributeError: + return 1 + + +# +# Now, the mapping from indices to decider functions +# +_decider_map = {0 : changed_since_last_build_node, + 1 : changed_since_last_build_alias, + 2 : changed_since_last_build_entry, + 3 : changed_since_last_build_state_changed, + 4 : decide_source, + 5 : decide_target, + 6 : changed_since_last_build_python} + +do_store_info = True + +# +# The new store_info subsystem for Nodes +# +# We would set and overwrite the store_info function +# before, but for being able to use slots (less memory!) we now have +# a dictionary of the different functions. Then in the Node +# subclasses we simply store the index to the info method that should be +# used by it. +# + +# +# First, the single info functions +# + +def store_info_pass(node): + pass + +def store_info_file(node): + # Merge our build information into the already-stored entry. + # This accommodates "chained builds" where a file that's a target + # in one build (SConstruct file) is a source in a different build. + # See test/chained-build.py for the use case. + if do_store_info: + node.dir.sconsign().store_info(node.name, node) + + +store_info_map = {0 : store_info_pass, + 1 : store_info_file} + # Classes for signature info for Nodes. class NodeInfoBase(object): @@ -114,11 +343,8 @@ class NodeInfoBase(object): Node subclasses should subclass NodeInfoBase to provide their own logic for dealing with their own Node-specific signature information. """ - current_version_id = 1 - def __init__(self, node=None): - # Create an object attribute from the class attribute so it ends up - # in the pickled data in the .sconsign file. - self._version_id = self.current_version_id + __slots__ = ('__weakref__',) + current_version_id = 2 def update(self, node): try: field_list = self.field_list @@ -138,13 +364,25 @@ class NodeInfoBase(object): def convert(self, node, val): pass def merge(self, other): - self.__dict__.update(other.__dict__) + """ + Merge the fields of another object into this object. Already existing + information is overwritten by the other instance's data. + WARNING: If a '__dict__' slot is added, it should be updated instead of + replaced. + """ + state = other.__getstate__() + self.__setstate__(state) def format(self, field_list=None, names=0): if field_list is None: try: field_list = self.field_list except AttributeError: - field_list = sorted(self.__dict__.keys()) + field_list = getattr(self, '__dict__', {}).keys() + for obj in type(self).mro(): + for slot in getattr(obj, '__slots__', ()): + if slot not in ('__weakref__', '__dict__'): + field_list.append(slot) + field_list.sort() fields = [] for field in field_list: try: @@ -157,6 +395,38 @@ class NodeInfoBase(object): fields.append(f) return fields + def __getstate__(self): + """ + Return all fields that shall be pickled. Walk the slots in the class + hierarchy and add those to the state dictionary. If a '__dict__' slot is + available, copy all entries to the dictionary. Also include the version + id, which is fixed for all instances of a class. + """ + state = getattr(self, '__dict__', {}).copy() + for obj in type(self).mro(): + for name in getattr(obj,'__slots__',()): + if hasattr(self, name): + state[name] = getattr(self, name) + + state['_version_id'] = self.current_version_id + try: + del state['__weakref__'] + except KeyError: + pass + return state + + def __setstate__(self, state): + """ + Restore the attributes from a pickled state. The version is discarded. + """ + # TODO check or discard version + del state['_version_id'] + + for key, value in state.items(): + if key not in ('__weakref__',): + setattr(self, key, value) + + class BuildInfoBase(object): """ The generic base class for build information for a Node. @@ -167,30 +437,106 @@ class BuildInfoBase(object): generic build stuff we have to track: sources, explicit dependencies, implicit dependencies, and action information. """ - current_version_id = 1 - def __init__(self, node=None): + __slots__ = ("bsourcesigs", "bdependsigs", "bimplicitsigs", "bactsig", + "bsources", "bdepends", "bact", "bimplicit", "__weakref__") + current_version_id = 2 + def __init__(self): # Create an object attribute from the class attribute so it ends up # in the pickled data in the .sconsign file. - self._version_id = self.current_version_id self.bsourcesigs = [] self.bdependsigs = [] self.bimplicitsigs = [] self.bactsig = None def merge(self, other): - self.__dict__.update(other.__dict__) + """ + Merge the fields of another object into this object. Already existing + information is overwritten by the other instance's data. + WARNING: If a '__dict__' slot is added, it should be updated instead of + replaced. + """ + state = other.__getstate__() + self.__setstate__(state) + + def __getstate__(self): + """ + Return all fields that shall be pickled. Walk the slots in the class + hierarchy and add those to the state dictionary. If a '__dict__' slot is + available, copy all entries to the dictionary. Also include the version + id, which is fixed for all instances of a class. + """ + state = getattr(self, '__dict__', {}).copy() + for obj in type(self).mro(): + for name in getattr(obj,'__slots__',()): + if hasattr(self, name): + state[name] = getattr(self, name) + + state['_version_id'] = self.current_version_id + try: + del state['__weakref__'] + except KeyError: + pass + return state + + def __setstate__(self, state): + """ + Restore the attributes from a pickled state. + """ + # TODO check or discard version + del state['_version_id'] + for key, value in state.items(): + if key not in ('__weakref__',): + setattr(self, key, value) class Node(object): """The base Node class, for entities that we know how to build, or use to build other Nodes. """ - if SCons.Memoize.use_memoizer: - __metaclass__ = SCons.Memoize.Memoized_Metaclass - - memoizer_counters = [] + __slots__ = ['sources', + 'sources_set', + '_specific_sources', + 'depends', + 'depends_set', + 'ignore', + 'ignore_set', + 'prerequisites', + 'implicit', + 'waiting_parents', + 'waiting_s_e', + 'ref_count', + 'wkids', + 'env', + 'state', + 'precious', + 'noclean', + 'nocache', + 'cached', + 'always_build', + 'includes', + 'attributes', + 'side_effect', + 'side_effects', + 'linked', + '_memo', + 'executor', + 'binfo', + 'ninfo', + 'builder', + 'is_explicit', + 'implicit_set', + 'changed_since_last_build', + 'store_info', + 'pseudo', + '_tags', + '_func_is_derived', + '_func_exists', + '_func_rexists', + '_func_get_contents', + '_func_target_from_source'] class Attrs(object): - pass + __slots__ = ('shared', '__dict__') + def __init__(self): if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.Node') @@ -234,7 +580,15 @@ class Node(object): self.side_effect = 0 # true iff this node is a side effect self.side_effects = [] # the side effects of building this target self.linked = 0 # is this node linked to the variant directory? - + self.changed_since_last_build = 0 + self.store_info = 0 + self._tags = None + self._func_is_derived = 1 + self._func_exists = 1 + self._func_rexists = 1 + self._func_get_contents = 0 + self._func_target_from_source = 0 + self.clear_memoized_values() # Let the interface in which the build engine is embedded @@ -248,8 +602,7 @@ class Node(object): def get_suffix(self): return '' - memoizer_counters.append(SCons.Memoize.CountValue('get_build_env')) - + @SCons.Memoize.CountMethodCall def get_build_env(self): """Fetch the appropriate Environment to build this node. """ @@ -418,7 +771,7 @@ class Node(object): pass else: self.ninfo.update(self) - self.store_info() + SCons.Node.store_info_map[self.store_info](self) def release_target_info(self): """Called just after this node has been marked @@ -546,7 +899,7 @@ class Node(object): example: source with source builders are not derived in this sense, and hence should not return true. """ - return self.has_builder() or self.side_effect + return _is_derived_map[self._func_is_derived](self) def alter_targets(self): """Return a list of alternate targets for this Node. @@ -706,7 +1059,7 @@ class Node(object): BuildInfo = BuildInfoBase def new_ninfo(self): - ninfo = self.NodeInfo(self) + ninfo = self.NodeInfo() return ninfo def get_ninfo(self): @@ -717,7 +1070,7 @@ class Node(object): return self.ninfo def new_binfo(self): - binfo = self.BuildInfo(self) + binfo = self.BuildInfo() return binfo def get_binfo(self): @@ -802,14 +1155,6 @@ class Node(object): def get_cachedir_csig(self): return self.get_csig() - def store_info(self): - """Make the build signature permanent (that is, store it in the - .sconsign file or equivalent).""" - pass - - def do_not_store_info(self): - pass - def get_stored_info(self): return None @@ -847,13 +1192,16 @@ class Node(object): def exists(self): """Does this node exists?""" - # All node exist by default: - return 1 + return _exists_map[self._func_exists](self) def rexists(self): """Does this node exist locally or in a repositiory?""" # There are no repositories by default: - return self.exists() + return _rexists_map[self._func_rexists](self) + + def get_contents(self): + """Fetch the contents of the entry.""" + return _get_contents_map[self._func_get_contents](self) def missing(self): return not self.is_derived() and \ @@ -941,11 +1289,10 @@ class Node(object): # build info that it's cached so we can re-calculate it. self.executor_cleanup() - memoizer_counters.append(SCons.Memoize.CountValue('_children_get')) - + @SCons.Memoize.CountMethodCall def _children_get(self): try: - return self._memo['children_get'] + return self._memo['_children_get'] except KeyError: pass @@ -976,7 +1323,7 @@ class Node(object): else: children = self.all_children(scan=0) - self._memo['children_get'] = children + self._memo['_children_get'] = children return children def all_children(self, scan=1): @@ -1016,9 +1363,6 @@ class Node(object): def get_state(self): return self.state - def state_has_changed(self, target, prev_ni): - return (self.state != SCons.Node.up_to_date) - def get_env(self): env = self.env if not env: @@ -1026,28 +1370,28 @@ class Node(object): env = SCons.Defaults.DefaultEnvironment() return env - def changed_since_last_build(self, target, prev_ni): - """ - - Must be overridden in a specific subclass to return True if this - Node (a dependency) has changed since the last time it was used - to build the specified target. prev_ni is this Node's state (for - example, its file timestamp, length, maybe content signature) - as of the last time the target was built. - - Note that this method is called through the dependency, not the - target, because a dependency Node must be able to use its own - logic to decide if it changed. For example, File Nodes need to - obey if we're configured to use timestamps, but Python Value Nodes - never use timestamps and always use the content. If this method - were called through the target, then each Node's implementation - of this method would have to have more complicated logic to - handle all the different Node types on which it might depend. - """ - raise NotImplementedError - def Decider(self, function): - SCons.Util.AddMethod(self, function, 'changed_since_last_build') + foundkey = None + for k, v in _decider_map.iteritems(): + if v == function: + foundkey = k + break + if not foundkey: + foundkey = len(_decider_map) + _decider_map[foundkey] = function + self.changed_since_last_build = foundkey + + def Tag(self, key, value): + """ Add a user-defined tag. """ + if not self._tags: + self._tags = {} + self._tags[key] = value + + def GetTag(self, key): + """ Return a user-defined tag. """ + if not self._tags: + return None + return self._tags.get(key, None) def changed(self, node=None, allowcache=False): """ @@ -1095,7 +1439,7 @@ class Node(object): result = True for child, prev_ni in zip(children, then): - if child.changed_since_last_build(self, prev_ni): + if _decider_map[child.changed_since_last_build](child, self, prev_ni): if t: Trace(': %s changed' % child) result = True @@ -1266,7 +1610,7 @@ class Node(object): for k in new_bkids: if not k in old_bkids: lines.append("`%s' is a new dependency\n" % stringify(k)) - elif k.changed_since_last_build(self, osig[k]): + elif _decider_map[k.changed_since_last_build](k, self, osig[k]): lines.append("`%s' changed\n" % stringify(k)) if len(lines) == 0 and old_bkids != new_bkids: diff --git a/src/engine/SCons/PathList.py b/src/engine/SCons/PathList.py index f3de57c..350e1ac 100644 --- a/src/engine/SCons/PathList.py +++ b/src/engine/SCons/PathList.py @@ -171,11 +171,6 @@ class PathListCache(object): cheaply avoid re-parsing both values of CPPPATH by using the common value from this cache. """ - if SCons.Memoize.use_memoizer: - __metaclass__ = SCons.Memoize.Memoized_Metaclass - - memoizer_counters = [] - def __init__(self): self._memo = {} @@ -196,8 +191,7 @@ class PathListCache(object): pathlist = tuple(SCons.Util.flatten(pathlist)) return pathlist - memoizer_counters.append(SCons.Memoize.CountDict('PathList', _PathList_key)) - + @SCons.Memoize.CountDictCall(_PathList_key) def PathList(self, pathlist): """ Returns the cached _PathList object for the specified pathlist, diff --git a/src/engine/SCons/SConf.py b/src/engine/SCons/SConf.py index 87432ef..8bce8ce 100644 --- a/src/engine/SCons/SConf.py +++ b/src/engine/SCons/SConf.py @@ -175,8 +175,11 @@ class SConfBuildInfo(SCons.Node.FS.FileBuildInfo): are result (did the builder succeed last time?) and string, which contains messages of the original build phase. """ - result = None # -> 0/None -> no error, != 0 error - string = None # the stdout / stderr output when building the target + __slots__ = ('result', 'string') + + def __init__(self): + self.result = None # -> 0/None -> no error, != 0 error + self.string = None # the stdout / stderr output when building the target def set_build_result(self, result, string): self.result = result @@ -352,8 +355,10 @@ class SConfBuildTask(SCons.Taskmaster.AlwaysTask): raise SCons.Errors.ExplicitExit(self.targets[0],exc_value.code) except Exception, e: for t in self.targets: - binfo = t.get_binfo() - binfo.__class__ = SConfBuildInfo + #binfo = t.get_binfo() + #binfo.__class__ = SConfBuildInfo + binfo = SConfBuildInfo() + binfo.merge(t.get_binfo()) binfo.set_build_result(1, s.getvalue()) sconsign_entry = SCons.SConsign.SConsignEntry() sconsign_entry.binfo = binfo @@ -370,8 +375,10 @@ class SConfBuildTask(SCons.Taskmaster.AlwaysTask): raise e else: for t in self.targets: - binfo = t.get_binfo() - binfo.__class__ = SConfBuildInfo + #binfo = t.get_binfo() + #binfo.__class__ = SConfBuildInfo + binfo = SConfBuildInfo() + binfo.merge(t.get_binfo()) binfo.set_build_result(0, s.getvalue()) sconsign_entry = SCons.SConsign.SConsignEntry() sconsign_entry.binfo = binfo @@ -500,7 +507,7 @@ class SConfBase(object): # we override the store_info() method with a null place-holder # so we really control how it gets written. for n in nodes: - n.store_info = n.do_not_store_info + n.store_info = 0 if not hasattr(n, 'attributes'): n.attributes = SCons.Node.Node.Attrs() n.attributes.keep_targetinfo = 1 @@ -640,7 +647,7 @@ class SConfBase(object): ok = self.TryLink(text, extension) if( ok ): prog = self.lastTarget - pname = prog.path + pname = prog.get_internal_path() output = self.confdir.File(os.path.basename(pname)+'.out') node = self.env.Command(output, prog, [ [ pname, ">", "${TARGET}"] ]) ok = self.BuildNodes(node) @@ -684,7 +691,6 @@ class SConfBase(object): else: if not os.path.isdir( dirName ): os.makedirs( dirName ) - node._exists = 1 def _startup(self): """Private method. Set up logstream, and set the environment diff --git a/src/engine/SCons/SConfTests.py b/src/engine/SCons/SConfTests.py index 1c4b401..233ee78 100644 --- a/src/engine/SCons/SConfTests.py +++ b/src/engine/SCons/SConfTests.py @@ -221,8 +221,6 @@ class SConfTestCase(unittest.TestCase): pass def get_stored_info(self): pass - def do_not_store_info(self): - pass def get_executor(self): class Executor(object): def __init__(self, targets): diff --git a/src/engine/SCons/SConsign.py b/src/engine/SCons/SConsign.py index 6555fcb..74ee804 100644 --- a/src/engine/SCons/SConsign.py +++ b/src/engine/SCons/SConsign.py @@ -122,16 +122,40 @@ class SConsignEntry(object): XXX As coded below, we do expect a '.binfo' attribute to be added, but we'll probably generalize this in the next refactorings. """ - current_version_id = 1 + __slots__ = ("binfo", "ninfo", "__weakref__") + current_version_id = 2 + def __init__(self): # Create an object attribute from the class attribute so it ends up # in the pickled data in the .sconsign file. - _version_id = self.current_version_id + #_version_id = self.current_version_id + pass + def convert_to_sconsign(self): self.binfo.convert_to_sconsign() + def convert_from_sconsign(self, dir, name): self.binfo.convert_from_sconsign(dir, name) + def __getstate__(self): + state = getattr(self, '__dict__', {}).copy() + for obj in type(self).mro(): + for name in getattr(obj,'__slots__',()): + if hasattr(self, name): + state[name] = getattr(self, name) + + state['_version_id'] = self.current_version_id + try: + del state['__weakref__'] + except KeyError: + pass + return state + + def __setstate__(self, state): + for key, value in state.items(): + if key not in ('_version_id','__weakref__'): + setattr(self, key, value) + class Base(object): """ This is the controlling class for the signatures for the collection of @@ -202,7 +226,7 @@ class DB(Base): # Read using the path relative to the top of the Repository # (self.dir.tpath) from which we're fetching the signature # information. - path = normcase(dir.tpath) + path = normcase(dir.get_tpath()) try: rawentries = db[path] except KeyError: @@ -217,7 +241,7 @@ class DB(Base): raise except Exception, e: SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning, - "Ignoring corrupt sconsign entry : %s (%s)\n"%(self.dir.tpath, e)) + "Ignoring corrupt sconsign entry : %s (%s)\n"%(self.dir.get_tpath(), e)) for key, entry in self.entries.items(): entry.convert_from_sconsign(dir, key) @@ -244,7 +268,7 @@ class DB(Base): # directory (self.dir.path), not relative to the top of # the Repository; we only write to our own .sconsign file, # not to .sconsign files in Repositories. - path = normcase(self.dir.path) + path = normcase(self.dir.get_internal_path()) for key, entry in self.entries.items(): entry.convert_to_sconsign() db[path] = pickle.dumps(self.entries, 1) @@ -287,7 +311,7 @@ class DirFile(Dir): """ self.dir = dir - self.sconsign = os.path.join(dir.path, '.sconsign') + self.sconsign = os.path.join(dir.get_internal_path(), '.sconsign') try: fp = open(self.sconsign, 'rb') @@ -323,7 +347,7 @@ class DirFile(Dir): self.merge() - temp = os.path.join(self.dir.path, '.scons%d' % os.getpid()) + temp = os.path.join(self.dir.get_internal_path(), '.scons%d' % os.getpid()) try: file = open(temp, 'wb') fname = temp diff --git a/src/engine/SCons/SConsignTests.py b/src/engine/SCons/SConsignTests.py index f71e53e..d40a7b6 100644 --- a/src/engine/SCons/SConsignTests.py +++ b/src/engine/SCons/SConsignTests.py @@ -62,6 +62,10 @@ class DummyNode(object): return self.binfo def get_binfo(self): return self.binfo + def get_internal_path(self): + return self.path + def get_tpath(self): + return self.tpath class SConsignTestCase(unittest.TestCase): def setUp(self): diff --git a/src/engine/SCons/Scanner/CTests.py b/src/engine/SCons/Scanner/CTests.py index 6418754..9c7df12 100644 --- a/src/engine/SCons/Scanner/CTests.py +++ b/src/engine/SCons/Scanner/CTests.py @@ -270,17 +270,18 @@ class CScannerTestCase5(unittest.TestCase): path = s.path(env) n = env.File('f3.cpp') - def my_rexists(s=n): - s.rexists_called = 1 - return s.old_rexists() - setattr(n, 'old_rexists', n.rexists) - setattr(n, 'rexists', my_rexists) + def my_rexists(s): + s.Tag('rexists_called', 1) + return SCons.Node._rexists_map[s.GetTag('old_rexists')](s) + n.Tag('old_rexists', n._func_rexists) + SCons.Node._rexists_map[3] = my_rexists + n._func_rexists = 3 deps = s(n, env, path) # Make sure rexists() got called on the file node being # scanned, essential for cooperation with VariantDir functionality. - assert n.rexists_called + assert n.GetTag('rexists_called') headers = ['f1.h', 'f2.h', 'f3-test.h', 'd1/f1.h', 'd1/f2.h', 'd1/f3-test.h'] diff --git a/src/engine/SCons/Scanner/Dir.py b/src/engine/SCons/Scanner/Dir.py index 1cecfb7..cbfb6fb 100644 --- a/src/engine/SCons/Scanner/Dir.py +++ b/src/engine/SCons/Scanner/Dir.py @@ -77,7 +77,7 @@ def scan_on_disk(node, env, path=()): that and then call the in-memory scanning function. """ try: - flist = node.fs.listdir(node.abspath) + flist = node.fs.listdir(node.get_abspath()) except (IOError, OSError): return [] e = node.Entry diff --git a/src/engine/SCons/Scanner/FortranTests.py b/src/engine/SCons/Scanner/FortranTests.py index 252da64..aaefa79 100644 --- a/src/engine/SCons/Scanner/FortranTests.py +++ b/src/engine/SCons/Scanner/FortranTests.py @@ -356,17 +356,18 @@ class FortranScannerTestCase9(unittest.TestCase): path = s.path(env) n = env.File('fff3.f') - def my_rexists(s=n): - s.rexists_called = 1 - return s.old_rexists() - setattr(n, 'old_rexists', n.rexists) - setattr(n, 'rexists', my_rexists) + def my_rexists(s): + s.Tag('rexists_called', 1) + return SCons.Node._rexists_map[s.GetTag('old_rexists')](s) + n.Tag('old_rexists', n._func_rexists) + SCons.Node._rexists_map[3] = my_rexists + n._func_rexists = 3 deps = s(n, env, path) # Make sure rexists() got called on the file node being # scanned, essential for cooperation with VariantDir functionality. - assert n.rexists_called + assert n.GetTag('rexists_called') headers = ['d1/f3.f', 'f3.f'] deps_match(self, deps, headers) diff --git a/src/engine/SCons/Scanner/IDLTests.py b/src/engine/SCons/Scanner/IDLTests.py index 675c70c..227799e 100644 --- a/src/engine/SCons/Scanner/IDLTests.py +++ b/src/engine/SCons/Scanner/IDLTests.py @@ -290,17 +290,18 @@ class IDLScannerTestCase5(unittest.TestCase): path = s.path(env) n = env.File('t3.idl') - def my_rexists(s=n): - s.rexists_called = 1 - return s.old_rexists() - setattr(n, 'old_rexists', n.rexists) - setattr(n, 'rexists', my_rexists) + def my_rexists(s): + s.Tag('rexists_called', 1) + return SCons.Node._rexists_map[s.GetTag('old_rexists')](s) + n.Tag('old_rexists', n._func_rexists) + SCons.Node._rexists_map[3] = my_rexists + n._func_rexists = 3 deps = s(n, env, path) # Make sure rexists() got called on the file node being # scanned, essential for cooperation with VariantDir functionality. - assert n.rexists_called + assert n.GetTag('rexists_called') headers = ['d1/f1.idl', 'd1/f2.idl', 'f1.idl', 'f2.idl', 'f3-test.idl', diff --git a/src/engine/SCons/Script/Main.py b/src/engine/SCons/Script/Main.py index c7a9d27..323fba9 100644 --- a/src/engine/SCons/Script/Main.py +++ b/src/engine/SCons/Script/Main.py @@ -214,7 +214,7 @@ class BuildTask(SCons.Taskmaster.OutOfDateTask): if self.top and not t.has_builder() and not t.side_effect: if not t.exists(): if t.__class__.__name__ in ('File', 'Dir', 'Entry'): - errstr="Do not know how to make %s target `%s' (%s)." % (t.__class__.__name__, t, t.abspath) + errstr="Do not know how to make %s target `%s' (%s)." % (t.__class__.__name__, t, t.get_abspath()) else: # Alias or Python or ... errstr="Do not know how to make %s target `%s'." % (t.__class__.__name__, t) sys.stderr.write("scons: *** " + errstr) @@ -351,7 +351,7 @@ class CleanTask(SCons.Taskmaster.AlwaysTask): if target in SCons.Environment.CleanTargets: files = SCons.Environment.CleanTargets[target] for f in files: - self.fs_delete(f.abspath, str(f), remove) + self.fs_delete(f.get_abspath(), str(f), remove) def show(self): for t in self._get_files_to_clean(): @@ -672,7 +672,7 @@ def _set_debug_values(options): if "prepare" in debug_values: SCons.Taskmaster.print_prepare = 1 if "duplicate" in debug_values: - SCons.Node.FS.print_duplicate = 1 + SCons.Node.print_duplicate = 1 def _create_path(plist): path = '.' @@ -946,9 +946,9 @@ def _main(parser): progress_display.set_mode(0) if options.site_dir: - _load_site_scons_dir(d.path, options.site_dir) + _load_site_scons_dir(d.get_internal_path(), options.site_dir) elif not options.no_site_dir: - _load_all_site_scons_dirs(d.path) + _load_all_site_scons_dirs(d.get_internal_path()) if options.include_dir: sys.path = options.include_dir + sys.path @@ -1111,7 +1111,6 @@ def _build_targets(fs, options, targets, target_top): display.set_mode(not options.silent) SCons.Action.print_actions = not options.silent SCons.Action.execute_actions = not options.no_exec - SCons.Node.FS.do_store_info = not options.no_exec SCons.Node.do_store_info = not options.no_exec SCons.SConf.dryrun = options.no_exec diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py index f4a7f07..0a5720d 100644 --- a/src/engine/SCons/Script/SConscript.py +++ b/src/engine/SCons/Script/SConscript.py @@ -265,7 +265,7 @@ def _SConscript(fs, *files, **kw): call_stack[-1].globals.update({__file__:old_file}) else: SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning, - "Ignoring missing SConscript '%s'" % f.path) + "Ignoring missing SConscript '%s'" % f.get_internal_path()) finally: SCons.Script.sconscript_reading = SCons.Script.sconscript_reading - 1 @@ -438,7 +438,7 @@ class SConsEnvironment(SCons.Environment.Base): fname = fn.get_path(src_dir) files = [os.path.join(str(variant_dir), fname)] else: - files = [fn.abspath] + files = [fn.get_abspath()] kw['src_dir'] = variant_dir self.fs.VariantDir(variant_dir, src_dir, duplicate) diff --git a/src/engine/SCons/Taskmaster.py b/src/engine/SCons/Taskmaster.py index 5de1cda..345534e 100644 --- a/src/engine/SCons/Taskmaster.py +++ b/src/engine/SCons/Taskmaster.py @@ -242,7 +242,7 @@ class Task(object): # for t in cached_targets: try: - t.fs.unlink(t.path) + t.fs.unlink(t.get_internal_path()) except (IOError, OSError): pass self.targets[0].build() diff --git a/src/engine/SCons/Tool/__init__.py b/src/engine/SCons/Tool/__init__.py index 2621cf9..efd2e33 100644 --- a/src/engine/SCons/Tool/__init__.py +++ b/src/engine/SCons/Tool/__init__.py @@ -340,7 +340,7 @@ symlinks for the platform we are on""" if version: # here we need the full pathname so the links end up in the right directory - libname = getattr(target[0].attributes, 'shlibpath', target[0].path) + libname = getattr(target[0].attributes, 'shlibpath', target[0].get_internal_path()) if Verbose: print "VerShLib: target lib is = ", libname print "VerShLib: name is = ", target[0].name diff --git a/src/engine/SCons/Tool/docbook/__init__.py b/src/engine/SCons/Tool/docbook/__init__.py index 26a1a95..aead43c 100644 --- a/src/engine/SCons/Tool/docbook/__init__.py +++ b/src/engine/SCons/Tool/docbook/__init__.py @@ -448,7 +448,7 @@ def DocbookEpub(env, target, source=None, *args, **kw): Ensure all the resources in the manifest are present in the OEBPS directory. """ hrefs = [] - content_file = os.path.join(source[0].abspath, 'content.opf') + content_file = os.path.join(source[0].get_abspath(), 'content.opf') if not os.path.isfile(content_file): return @@ -491,9 +491,9 @@ def DocbookEpub(env, target, source=None, *args, **kw): for href in hrefs: # If the resource was not already created by DocBook XSL itself, # copy it into the OEBPS folder - referenced_file = os.path.join(source[0].abspath, href) + referenced_file = os.path.join(source[0].get_abspath(), href) if not os.path.exists(referenced_file): - shutil.copy(href, os.path.join(source[0].abspath, href)) + shutil.copy(href, os.path.join(source[0].get_abspath(), href)) # Init list of targets/sources target, source = __extend_targets_sources(target, source) diff --git a/src/engine/SCons/Tool/mslink.py b/src/engine/SCons/Tool/mslink.py index 37af34e..69baa43 100644 --- a/src/engine/SCons/Tool/mslink.py +++ b/src/engine/SCons/Tool/mslink.py @@ -208,7 +208,7 @@ def embedManifestDllCheck(target, source, env): """Function run by embedManifestDllCheckAction to check for existence of manifest and other conditions, and embed the manifest by calling embedManifestDllAction if so.""" if env.get('WINDOWS_EMBED_MANIFEST', 0): - manifestSrc = target[0].abspath + '.manifest' + manifestSrc = target[0].get_abspath() + '.manifest' if os.path.exists(manifestSrc): ret = (embedManifestDllAction) ([target[0]],None,env) if ret: @@ -222,7 +222,7 @@ def embedManifestExeCheck(target, source, env): """Function run by embedManifestExeCheckAction to check for existence of manifest and other conditions, and embed the manifest by calling embedManifestExeAction if so.""" if env.get('WINDOWS_EMBED_MANIFEST', 0): - manifestSrc = target[0].abspath + '.manifest' + manifestSrc = target[0].get_abspath() + '.manifest' if os.path.exists(manifestSrc): ret = (embedManifestExeAction) ([target[0]],None,env) if ret: diff --git a/src/engine/SCons/Tool/packaging/__init__.py b/src/engine/SCons/Tool/packaging/__init__.py index 95311a2..1a979ab 100644 --- a/src/engine/SCons/Tool/packaging/__init__.py +++ b/src/engine/SCons/Tool/packaging/__init__.py @@ -80,7 +80,7 @@ def Tag(env, target, source, *more_tags, **kw_tags): #if not k.startswith('PACKAGING_'): if k[:10] != 'PACKAGING_': k='PACKAGING_'+k - setattr(t, k, v) + t.Tag(k, v) def Package(env, target=None, source=None, **kw): """ Entry point for the package tool. @@ -235,9 +235,11 @@ def copy_attr(f1, f2): #pattrs = [x for x in dir(f1) if not hasattr(f2, x) and\ # x.startswith('PACKAGING_')] copyit = lambda x: not hasattr(f2, x) and x[:10] == 'PACKAGING_' - pattrs = list(filter(copyit, dir(f1))) - for attr in pattrs: - setattr(f2, attr, getattr(f1, attr)) + if f1._tags: + pattrs = list(filter(copyit, f1._tags)) + for attr in pattrs: + f2.Tag(attr, f1.GetTag(attr)) + def putintopackageroot(target, source, env, pkgroot, honor_install_location=1): """ Uses the CopyAs builder to copy all source files to the directory given in pkgroot. @@ -262,9 +264,9 @@ def putintopackageroot(target, source, env, pkgroot, honor_install_location=1): if file.is_under(pkgroot): new_source.append(file) else: - if hasattr(file, 'PACKAGING_INSTALL_LOCATION') and\ + if file.GetTag('PACKAGING_INSTALL_LOCATION') and\ honor_install_location: - new_name=make_path_relative(file.PACKAGING_INSTALL_LOCATION) + new_name=make_path_relative(file.GetTag('PACKAGING_INSTALL_LOCATION')) else: new_name=make_path_relative(file.get_path()) @@ -301,7 +303,7 @@ def stripinstallbuilder(target, source, env): for ss in s.sources: n_source.append(ss) copy_attr(s, ss) - setattr(ss, 'PACKAGING_INSTALL_LOCATION', s.get_path()) + ss.Tag('PACKAGING_INSTALL_LOCATION', s.get_path()) return (target, n_source) diff --git a/src/engine/SCons/Tool/packaging/ipk.py b/src/engine/SCons/Tool/packaging/ipk.py index 6549445..84f4e20 100644 --- a/src/engine/SCons/Tool/packaging/ipk.py +++ b/src/engine/SCons/Tool/packaging/ipk.py @@ -120,7 +120,7 @@ def build_specfiles(source, target, env): return opened_files[needle] except KeyError: file=filter(lambda x: x.get_path().rfind(needle)!=-1, haystack)[0] - opened_files[needle]=open(file.abspath, 'w') + opened_files[needle]=open(file.get_abspath(), 'w') return opened_files[needle] control_file=open_file('control', target) diff --git a/src/engine/SCons/Tool/packaging/msi.py b/src/engine/SCons/Tool/packaging/msi.py index fe78c9c..172038f 100644 --- a/src/engine/SCons/Tool/packaging/msi.py +++ b/src/engine/SCons/Tool/packaging/msi.py @@ -189,7 +189,7 @@ def build_wxsfile(target, source, env): """ compiles a .wxs file from the keywords given in env['msi_spec'] and by analyzing the tree of source nodes and their tags. """ - file = open(target[0].abspath, 'w') + file = open(target[0].get_abspath(), 'w') try: # Create a document with the Wix root tag diff --git a/src/engine/SCons/Tool/packaging/rpm.py b/src/engine/SCons/Tool/packaging/rpm.py index 2bc3063..656d8fb 100644 --- a/src/engine/SCons/Tool/packaging/rpm.py +++ b/src/engine/SCons/Tool/packaging/rpm.py @@ -130,8 +130,7 @@ def build_specfile(target, source, env): """ Builds a RPM specfile from a dictionary with string metadata and by analyzing a tree of nodes. """ - file = open(target[0].abspath, 'w') - str = "" + file = open(target[0].get_abspath(), 'w') try: file.write( build_specfile_header(env) ) @@ -279,7 +278,9 @@ def build_specfile_filesection(spec, files): tags = {} for k in supported_tags.keys(): try: - tags[k]=getattr(file, k) + v = file.GetTag(k) + if v: + tags[k] = v except AttributeError: pass @@ -287,7 +288,7 @@ def build_specfile_filesection(spec, files): str = str + SimpleTagCompiler(supported_tags, mandatory=0).compile( tags ) str = str + ' ' - str = str + file.PACKAGING_INSTALL_LOCATION + str = str + file.GetTag('PACKAGING_INSTALL_LOCATION') str = str + '\n\n' return str diff --git a/src/engine/SCons/Tool/rpm.py b/src/engine/SCons/Tool/rpm.py index 1f6eafe..b7d65a8 100644 --- a/src/engine/SCons/Tool/rpm.py +++ b/src/engine/SCons/Tool/rpm.py @@ -51,11 +51,11 @@ def get_cmd(source, env): if SCons.Util.is_List(source): tar_file_with_included_specfile = source[0] return "%s %s %s"%(env['RPM'], env['RPMFLAGS'], - tar_file_with_included_specfile.abspath ) + tar_file_with_included_specfile.get_abspath() ) def build_rpm(target, source, env): # create a temporary rpm build root. - tmpdir = os.path.join( os.path.dirname( target[0].abspath ), 'rpmtemp' ) + tmpdir = os.path.join( os.path.dirname( target[0].get_abspath() ), 'rpmtemp' ) if os.path.exists(tmpdir): shutil.rmtree(tmpdir) @@ -87,7 +87,7 @@ def build_rpm(target, source, env): expected = os.path.basename(input.get_path()) assert expected == rpm_output, "got %s but expected %s" % (rpm_output, expected) - shutil.copy( output, input.abspath ) + shutil.copy( output, input.get_abspath() ) # cleanup before leaving. diff --git a/src/engine/SCons/Tool/swig.py b/src/engine/SCons/Tool/swig.py index f166174..a315182 100644 --- a/src/engine/SCons/Tool/swig.py +++ b/src/engine/SCons/Tool/swig.py @@ -42,6 +42,7 @@ import SCons.Defaults import SCons.Scanner import SCons.Tool import SCons.Util +import SCons.Node SwigAction = SCons.Action.Action('$SWIGCOM', '$SWIGCOMSTR') @@ -117,9 +118,13 @@ def _swigEmitter(target, source, env): if outdir: java_files = [os.path.join(outdir, j) for j in java_files] java_files = list(map(env.fs.File, java_files)) + def t_from_s(t, p, s, x): + return t.dir + tsm = SCons.Node._target_from_source_map + tkey = len(tsm) + tsm[tkey] = t_from_s for jf in java_files: - t_from_s = lambda t, p, s, x: t.dir - SCons.Util.AddMethod(jf, t_from_s, 'target_from_source') + jf._func_target_from_source = tkey target.extend(java_files) return (target, source) diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py index 822d524..4890ba2 100644 --- a/src/engine/SCons/Util.py +++ b/src/engine/SCons/Util.py @@ -992,7 +992,7 @@ class Selector(OrderedDict): def __call__(self, env, source, ext=None): if ext is None: try: - ext = source[0].suffix + ext = source[0].get_suffix() except IndexError: ext = "" try: diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py index b0c15c5..795bc46 100644 --- a/src/engine/SCons/UtilTests.py +++ b/src/engine/SCons/UtilTests.py @@ -591,10 +591,11 @@ class UtilTestCase(unittest.TestCase): class MyNode(object): def __init__(self, name): self.name = name - self.suffix = os.path.splitext(name)[1] def __str__(self): return self.name + def get_suffix(self): + return os.path.splitext(self.name)[1] s = Selector({'a' : 'AAA', 'b' : 'BBB'}) assert s['a'] == 'AAA', s['a'] |