diff options
author | Steven Knight <knight@baldmt.com> | 2002-04-04 09:07:00 (GMT) |
---|---|---|
committer | Steven Knight <knight@baldmt.com> | 2002-04-04 09:07:00 (GMT) |
commit | 6603cc0d6643e529da45fef6f85fcc1a0fc02ea4 (patch) | |
tree | 4a27de7c3fd70e5af0f257ccc73aab38e343f156 /src | |
parent | a7669bc6a02999a3375c7e732a27ded5f9bb9935 (diff) | |
download | SCons-6603cc0d6643e529da45fef6f85fcc1a0fc02ea4.zip SCons-6603cc0d6643e529da45fef6f85fcc1a0fc02ea4.tar.gz SCons-6603cc0d6643e529da45fef6f85fcc1a0fc02ea4.tar.bz2 |
Fix --debug=tree for directory targets (Anthony Roach)
Diffstat (limited to 'src')
-rw-r--r-- | src/CHANGES.txt | 6 | ||||
-rw-r--r-- | src/RELEASE.txt | 7 | ||||
-rw-r--r-- | src/engine/SCons/Builder.py | 82 | ||||
-rw-r--r-- | src/engine/SCons/BuilderTests.py | 348 | ||||
-rw-r--r-- | src/engine/SCons/Node/FS.py | 71 | ||||
-rw-r--r-- | src/engine/SCons/Node/FSTests.py | 89 | ||||
-rw-r--r-- | src/engine/SCons/Node/NodeTests.py | 363 | ||||
-rw-r--r-- | src/engine/SCons/Node/__init__.py | 130 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/C.py | 57 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/CTests.py | 54 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/Prog.py | 87 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/ProgTests.py | 20 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/ScannerTests.py | 37 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/__init__.py | 20 | ||||
-rw-r--r-- | src/engine/SCons/Sig/SigTests.py | 16 | ||||
-rw-r--r-- | src/engine/SCons/Sig/__init__.py | 32 | ||||
-rw-r--r-- | src/engine/SCons/Taskmaster.py | 229 | ||||
-rw-r--r-- | src/engine/SCons/TaskmasterTests.py | 186 |
18 files changed, 786 insertions, 1048 deletions
diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 0fc473c..fee7642 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -14,6 +14,12 @@ RELEASE 0.07 - - Fix so that -c -n does *not* remove the targets! + From Anthony Roach: + + - Fix --debug=tree when used with directory targets. + + - Significant internal restructuring of Scanners and Taskmaster. + RELEASE 0.06 - Thu, 28 Mar 2002 01:24:29 -0600 diff --git a/src/RELEASE.txt b/src/RELEASE.txt index 9a6a5f8..41cd49b 100644 --- a/src/RELEASE.txt +++ b/src/RELEASE.txt @@ -20,15 +20,16 @@ more effectively, please sign up for the scons-users mailing list at: -RELEASE 0.06 - Thu, 28 Mar 2002 01:24:29 -0600 +RELEASE 0.07 - - This is the sixth alpha release of SCons. Please consult the + This is the seventh alpha release of SCons. Please consult the CHANGES.txt file for a list of specific changes since last release. Please note the following important changes since the previous release: - - Python functions as Builder actions now take Node objects, not + - Scanner functions now take four arguments. + strings, as arguments. The string representation of a Node object is the file name, so you should change your function actions to use the str() built-in function to fetch the file diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index bc90e19..e7af01c 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -41,7 +41,6 @@ import SCons.Node.FS import SCons.Util - def Builder(**kw): """A factory for builder objects.""" @@ -64,77 +63,72 @@ def _init_nodes(builder, env, tlist, slist): """Initialize lists of target and source nodes with all of the proper Builder information. """ - src_scanner = None - if slist: - src_key = slist[0].scanner_key() # the file suffix - src_scanner = env.get_scanner(src_key) - if src_scanner: - src_scanner = src_scanner.instance(env) - + for s in slist: + src_key = slist[0].scanner_key() # the file suffix + scanner = env.get_scanner(src_key) + if scanner: + s.source_scanner = scanner + for t in tlist: - t.cwd = SCons.Node.FS.default_fs.getcwd() # XXX + t.cwd = SCons.Node.FS.default_fs.getcwd() # XXX t.builder_set(builder) t.env_set(env) t.add_source(slist) if builder.scanner: - t.scanner_set(builder.scanner.instance(env)) - if src_scanner: - t.src_scanner_set(src_key, src_scanner) - - + t.target_scanner = builder.scanner class BuilderBase: """Base class for Builders, objects that create output nodes (files) from input nodes (files). """ - def __init__(self, name = None, - action = None, - prefix = '', - suffix = '', - src_suffix = '', + def __init__(self, name = None, + action = None, + prefix = '', + suffix = '', + src_suffix = '', node_factory = SCons.Node.FS.default_fs.File, target_factory = None, source_factory = None, scanner = None): if name is None: raise UserError, "You must specify a name for the builder." - self.name = name - self.action = SCons.Action.Action(action) + self.name = name + self.action = SCons.Action.Action(action) - self.prefix = prefix - self.suffix = suffix - self.src_suffix = src_suffix + self.prefix = prefix + self.suffix = suffix + self.src_suffix = src_suffix self.target_factory = target_factory or node_factory self.source_factory = source_factory or node_factory self.scanner = scanner if self.suffix and self.suffix[0] not in '.$': - self.suffix = '.' + self.suffix + self.suffix = '.' + self.suffix if self.src_suffix and self.src_suffix[0] not in '.$': - self.src_suffix = '.' + self.src_suffix + self.src_suffix = '.' + self.src_suffix def __cmp__(self, other): - return cmp(self.__dict__, other.__dict__) + return cmp(self.__dict__, other.__dict__) def _create_nodes(self, env, target = None, source = None): """Create and return lists of target and source nodes. """ - def adjustixes(files, pre, suf): - ret = [] + def adjustixes(files, pre, suf): + ret = [] if SCons.Util.is_String(files): files = string.split(files) if not SCons.Util.is_List(files): - files = [files] - for f in files: + files = [files] + for f in files: if SCons.Util.is_String(f): - if pre and f[:len(pre)] != pre: + if pre and f[:len(pre)] != pre: path, fn = os.path.split(os.path.normpath(f)) f = os.path.join(path, pre + fn) - if suf: - if f[-len(suf):] != suf: - f = f + suf - ret.append(f) - return ret + if suf: + if f[-len(suf):] != suf: + f = f + suf + ret.append(f) + return ret tlist = SCons.Node.arg2nodes(adjustixes(target, env.subst(self.prefix), @@ -160,9 +154,9 @@ class BuilderBase: def execute(self, **kw): - """Execute a builder's action to create an output object. - """ - return apply(self.action.execute, (), kw) + """Execute a builder's action to create an output object. + """ + return apply(self.action.execute, (), kw) def get_raw_contents(self, **kw): """Fetch the "contents" of the builder's action. @@ -241,10 +235,10 @@ class MultiStepBuilder(BuilderBase): """ def __init__(self, src_builder, name = None, - action = None, - prefix = '', - suffix = '', - src_suffix = '', + action = None, + prefix = '', + suffix = '', + src_suffix = '', node_factory = SCons.Node.FS.default_fs.File, target_factory = None, source_factory = None, diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py index 16c87a4..0527fe0 100644 --- a/src/engine/SCons/BuilderTests.py +++ b/src/engine/SCons/BuilderTests.py @@ -65,7 +65,6 @@ outfile = test.workpath('outfile') outfile2 = test.workpath('outfile2') show_string = None -instanced = None env_scanner = None count = 0 @@ -88,31 +87,31 @@ env = Environment() class BuilderTestCase(unittest.TestCase): def test__call__(self): - """Test calling a builder to establish source dependencies - """ - class Node: - def __init__(self, name): - self.name = name - self.sources = [] - self.builder = None - def __str__(self): - return self.name - def builder_set(self, builder): - self.builder = builder - def env_set(self, env, safe=0): - self.env = env - def add_source(self, source): - self.sources.extend(source) + """Test calling a builder to establish source dependencies + """ + class Node: + def __init__(self, name): + self.name = name + self.sources = [] + self.builder = None + def __str__(self): + return self.name + def builder_set(self, builder): + self.builder = builder + def env_set(self, env, safe=0): + self.env = env + def add_source(self, source): + self.sources.extend(source) def scanner_key(self): return self.name builder = SCons.Builder.Builder(name="builder", action="foo", node_factory=Node) - n1 = Node("n1"); - n2 = Node("n2"); - builder(env, target = n1, source = n2) - assert n1.env == env - assert n1.builder == builder - assert n1.sources == [n2] + n1 = Node("n1"); + n2 = Node("n2"); + builder(env, target = n1, source = n2) + assert n1.env == env + assert n1.builder == builder + assert n1.sources == [n2] assert not hasattr(n2, 'env') target = builder(env, target = 'n3', source = 'n4') @@ -159,12 +158,12 @@ class BuilderTestCase(unittest.TestCase): assert 0 def test_action(self): - """Test Builder creation + """Test Builder creation - Verify that we can retrieve the supplied action attribute. - """ - builder = SCons.Builder.Builder(name="builder", action="foo") - assert builder.action.command == "foo" + Verify that we can retrieve the supplied action attribute. + """ + builder = SCons.Builder.Builder(name="builder", action="foo") + assert builder.action.command == "foo" def test_generator(self): """Test Builder creation given a generator function.""" @@ -176,22 +175,22 @@ class BuilderTestCase(unittest.TestCase): assert builder.action.generator == generator def test_cmp(self): - """Test simple comparisons of Builder objects - """ - b1 = SCons.Builder.Builder(name="b1", src_suffix = '.o') - b2 = SCons.Builder.Builder(name="b1", src_suffix = '.o') - assert b1 == b2 - b3 = SCons.Builder.Builder(name="b3", src_suffix = '.x') - assert b1 != b3 - assert b2 != b3 + """Test simple comparisons of Builder objects + """ + b1 = SCons.Builder.Builder(name="b1", src_suffix = '.o') + b2 = SCons.Builder.Builder(name="b1", src_suffix = '.o') + assert b1 == b2 + b3 = SCons.Builder.Builder(name="b3", src_suffix = '.x') + assert b1 != b3 + assert b2 != b3 def test_execute(self): - """Test execution of simple Builder objects - - One Builder is a string that executes an external command, - one is an internal Python function, one is a list - containing one of each. - """ + """Test execution of simple Builder objects + + One Builder is a string that executes an external command, + one is an internal Python function, one is a list + containing one of each. + """ def MyBuilder(**kw): builder = apply(SCons.Builder.Builder, (), kw) @@ -200,54 +199,54 @@ class BuilderTestCase(unittest.TestCase): builder.action.show = no_show return builder - python = sys.executable + python = sys.executable - cmd1 = r'%s %s %s xyzzy' % (python, act_py, outfile) + cmd1 = r'%s %s %s xyzzy' % (python, act_py, outfile) builder = MyBuilder(action = cmd1, name = "cmd1") - r = builder.execute() - assert r == 0 - c = test.read(outfile, 'r') + r = builder.execute() + assert r == 0 + c = test.read(outfile, 'r') assert c == "act.py: 'xyzzy'\n", c - cmd2 = r'%s %s %s $TARGET' % (python, act_py, outfile) + cmd2 = r'%s %s %s $TARGET' % (python, act_py, outfile) builder = MyBuilder(action = cmd2, name = "cmd2") - r = builder.execute(target = 'foo') - assert r == 0 - c = test.read(outfile, 'r') + r = builder.execute(target = 'foo') + assert r == 0 + c = test.read(outfile, 'r') assert c == "act.py: 'foo'\n", c - cmd3 = r'%s %s %s ${TARGETS}' % (python, act_py, outfile) + cmd3 = r'%s %s %s ${TARGETS}' % (python, act_py, outfile) builder = MyBuilder(action = cmd3, name = "cmd3") - r = builder.execute(target = ['aaa', 'bbb']) - assert r == 0 - c = test.read(outfile, 'r') + r = builder.execute(target = ['aaa', 'bbb']) + assert r == 0 + c = test.read(outfile, 'r') assert c == "act.py: 'aaa' 'bbb'\n", c - cmd4 = r'%s %s %s $SOURCES' % (python, act_py, outfile) + cmd4 = r'%s %s %s $SOURCES' % (python, act_py, outfile) builder = MyBuilder(action = cmd4, name = "cmd4") - r = builder.execute(source = ['one', 'two']) - assert r == 0 - c = test.read(outfile, 'r') + r = builder.execute(source = ['one', 'two']) + assert r == 0 + c = test.read(outfile, 'r') assert c == "act.py: 'one' 'two'\n", c - cmd4 = r'%s %s %s ${SOURCES[:2]}' % (python, act_py, outfile) + cmd4 = r'%s %s %s ${SOURCES[:2]}' % (python, act_py, outfile) builder = MyBuilder(action = cmd4, name = "cmd4") - r = builder.execute(source = ['three', 'four', 'five']) - assert r == 0 - c = test.read(outfile, 'r') + r = builder.execute(source = ['three', 'four', 'five']) + assert r == 0 + c = test.read(outfile, 'r') assert c == "act.py: 'three' 'four'\n", c - cmd5 = r'%s %s %s $TARGET XYZZY' % (python, act_py, outfile) + cmd5 = r'%s %s %s $TARGET XYZZY' % (python, act_py, outfile) builder = MyBuilder(action = cmd5, name = "cmd5") - r = builder.execute(target = 'out5', env = {'ENV' : {'XYZZY' : 'xyzzy'}}) - assert r == 0 - c = test.read(outfile, 'r') + r = builder.execute(target = 'out5', env = {'ENV' : {'XYZZY' : 'xyzzy'}}) + assert r == 0 + c = test.read(outfile, 'r') assert c == "act.py: 'out5' 'XYZZY'\nact.py: 'xyzzy'\n", c class Obj: @@ -285,16 +284,16 @@ class BuilderTestCase(unittest.TestCase): global count count = 0 - def function1(**kw): + def function1(**kw): global count count = count + 1 if not type(kw['target']) is type([]): kw['target'] = [ kw['target'] ] for t in kw['target']: - open(t, 'w').write("function1\n") - return 1 + open(t, 'w').write("function1\n") + return 1 - builder = MyBuilder(action = function1, name = "function1") + builder = MyBuilder(action = function1, name = "function1") try: r = builder.execute(target = [outfile, outfile2]) except SCons.Errors.BuildError: @@ -302,50 +301,50 @@ class BuilderTestCase(unittest.TestCase): assert r == 1 assert count == 1 c = test.read(outfile, 'r') - assert c == "function1\n", c + assert c == "function1\n", c c = test.read(outfile2, 'r') - assert c == "function1\n", c - - class class1a: - def __init__(self, **kw): - open(kw['out'], 'w').write("class1a\n") - - builder = MyBuilder(action = class1a, name = "class1a") - r = builder.execute(out = outfile) - assert r.__class__ == class1a - c = test.read(outfile, 'r') - assert c == "class1a\n", c - - class class1b: - def __call__(self, **kw): - open(kw['out'], 'w').write("class1b\n") - return 2 - - builder = MyBuilder(action = class1b(), name = "class1b") - r = builder.execute(out = outfile) - assert r == 2 - c = test.read(outfile, 'r') - assert c == "class1b\n", c - - cmd2 = r'%s %s %s syzygy' % (python, act_py, outfile) - - def function2(**kw): - open(kw['out'], 'a').write("function2\n") - return 0 - - class class2a: - def __call__(self, **kw): - open(kw['out'], 'a').write("class2a\n") - return 0 - - class class2b: - def __init__(self, **kw): - open(kw['out'], 'a').write("class2b\n") - - builder = MyBuilder(action = [cmd2, function2, class2a(), class2b], name = "clist") - r = builder.execute(out = outfile) - assert r.__class__ == class2b - c = test.read(outfile, 'r') + assert c == "function1\n", c + + class class1a: + def __init__(self, **kw): + open(kw['out'], 'w').write("class1a\n") + + builder = MyBuilder(action = class1a, name = "class1a") + r = builder.execute(out = outfile) + assert r.__class__ == class1a + c = test.read(outfile, 'r') + assert c == "class1a\n", c + + class class1b: + def __call__(self, **kw): + open(kw['out'], 'w').write("class1b\n") + return 2 + + builder = MyBuilder(action = class1b(), name = "class1b") + r = builder.execute(out = outfile) + assert r == 2 + c = test.read(outfile, 'r') + assert c == "class1b\n", c + + cmd2 = r'%s %s %s syzygy' % (python, act_py, outfile) + + def function2(**kw): + open(kw['out'], 'a').write("function2\n") + return 0 + + class class2a: + def __call__(self, **kw): + open(kw['out'], 'a').write("class2a\n") + return 0 + + class class2b: + def __init__(self, **kw): + open(kw['out'], 'a').write("class2b\n") + + builder = MyBuilder(action = [cmd2, function2, class2a(), class2b], name = "clist") + r = builder.execute(out = outfile) + assert r.__class__ == class2b + c = test.read(outfile, 'r') assert c == "act.py: 'syzygy'\nfunction2\nclass2a\nclass2b\n", c if os.name == 'nt': @@ -390,53 +389,53 @@ class BuilderTestCase(unittest.TestCase): assert contents == "foo\177\036\000\177\037\000d\000\000Sbar", repr(contents) def test_node_factory(self): - """Test a Builder that creates nodes of a specified class - """ - class Foo: - pass - def FooFactory(target): + """Test a Builder that creates nodes of a specified class + """ + class Foo: + pass + def FooFactory(target): global Foo - return Foo(target) - builder = SCons.Builder.Builder(name = "builder", node_factory = FooFactory) - assert builder.target_factory is FooFactory - assert builder.source_factory is FooFactory + return Foo(target) + builder = SCons.Builder.Builder(name = "builder", node_factory = FooFactory) + assert builder.target_factory is FooFactory + assert builder.source_factory is FooFactory def test_target_factory(self): - """Test a Builder that creates target nodes of a specified class - """ - class Foo: - pass - def FooFactory(target): + """Test a Builder that creates target nodes of a specified class + """ + class Foo: + pass + def FooFactory(target): global Foo - return Foo(target) - builder = SCons.Builder.Builder(name = "builder", target_factory = FooFactory) - assert builder.target_factory is FooFactory - assert not builder.source_factory is FooFactory + return Foo(target) + builder = SCons.Builder.Builder(name = "builder", target_factory = FooFactory) + assert builder.target_factory is FooFactory + assert not builder.source_factory is FooFactory def test_source_factory(self): - """Test a Builder that creates source nodes of a specified class - """ - class Foo: - pass - def FooFactory(source): + """Test a Builder that creates source nodes of a specified class + """ + class Foo: + pass + def FooFactory(source): global Foo - return Foo(source) - builder = SCons.Builder.Builder(name = "builder", source_factory = FooFactory) - assert not builder.target_factory is FooFactory - assert builder.source_factory is FooFactory + return Foo(source) + builder = SCons.Builder.Builder(name = "builder", source_factory = FooFactory) + assert not builder.target_factory is FooFactory + assert builder.source_factory is FooFactory def test_prefix(self): - """Test Builder creation with a specified target prefix - - Make sure that there is no '.' separator appended. - """ - builder = SCons.Builder.Builder(name = "builder", prefix = 'lib.') - assert builder.prefix == 'lib.' - builder = SCons.Builder.Builder(name = "builder", prefix = 'lib') - assert builder.prefix == 'lib' - tgt = builder(env, target = 'tgt1', source = 'src1') - assert tgt.path == 'libtgt1', \ - "Target has unexpected name: %s" % tgt.path + """Test Builder creation with a specified target prefix + + Make sure that there is no '.' separator appended. + """ + builder = SCons.Builder.Builder(name = "builder", prefix = 'lib.') + assert builder.prefix == 'lib.' + builder = SCons.Builder.Builder(name = "builder", prefix = 'lib') + assert builder.prefix == 'lib' + tgt = builder(env, target = 'tgt1', source = 'src1') + assert tgt.path == 'libtgt1', \ + "Target has unexpected name: %s" % tgt.path tgts = builder(env, target = 'tgt2a tgt2b', source = 'src2') assert tgts[0].path == 'libtgt2a', \ "Target has unexpected name: %s" % tgts[0].path @@ -482,18 +481,18 @@ class BuilderTestCase(unittest.TestCase): assert b5.src_suffixes(env) == ['.y'], b5.src_suffixes(env) def test_suffix(self): - """Test Builder creation with a specified target suffix - - Make sure that the '.' separator is appended to the - beginning if it isn't already present. - """ - builder = SCons.Builder.Builder(name = "builder", suffix = '.o') - assert builder.suffix == '.o' - builder = SCons.Builder.Builder(name = "builder", suffix = 'o') - assert builder.suffix == '.o' - tgt = builder(env, target = 'tgt3', source = 'src3') - assert tgt.path == 'tgt3.o', \ - "Target has unexpected name: %s" % tgt[0].path + """Test Builder creation with a specified target suffix + + Make sure that the '.' separator is appended to the + beginning if it isn't already present. + """ + builder = SCons.Builder.Builder(name = "builder", suffix = '.o') + assert builder.suffix == '.o' + builder = SCons.Builder.Builder(name = "builder", suffix = 'o') + assert builder.suffix == '.o' + tgt = builder(env, target = 'tgt3', source = 'src3') + assert tgt.path == 'tgt3.o', \ + "Target has unexpected name: %s" % tgt[0].path tgts = builder(env, target = 'tgt4a tgt4b', source = 'src4') assert tgts[0].path == 'tgt4a.o', \ "Target has unexpected name: %s" % tgts[0].path @@ -517,9 +516,9 @@ class BuilderTestCase(unittest.TestCase): builder = SCons.Builder.Builder(action = function2, name = "function2") tgts = builder(env, target = [outfile, outfile2], source = 'foo') - try: + try: r = tgts[0].builder.execute(target = tgts) - except SCons.Errors.BuildError: + except SCons.Errors.BuildError: pass c = test.read(outfile, 'r') assert c == "function2\n", c @@ -652,17 +651,12 @@ class BuilderTestCase(unittest.TestCase): """Testing ability to set a target scanner through a builder.""" global instanced class TestScanner: - def instance(self, env): - global instanced - instanced = 1 - return self + pass scn = TestScanner() builder = SCons.Builder.Builder(name = "builder", scanner=scn) tgt = builder(env, target='foo', source='bar') - assert tgt.scanner == scn, tgt.scanner - assert instanced + assert tgt.target_scanner == scn, tgt.target_scanner - instanced = None builder1 = SCons.Builder.Builder(name = "builder1", action='foo', src_suffix='.bar', @@ -672,8 +666,7 @@ class BuilderTestCase(unittest.TestCase): src_builder = builder1, scanner = scn) tgt = builder2(env, target='baz', source='test.bar test2.foo test3.txt') - assert tgt.scanner == scn, tgt.scanner - assert instanced + assert tgt.target_scanner == scn, tgt.target_scanner def test_src_scanner(slf): """Testing ability to set a source file scanner through a builder.""" @@ -686,10 +679,11 @@ class BuilderTestCase(unittest.TestCase): env_scanner = TestScanner() builder = SCons.Builder.Builder(name = "builder", action='action') tgt = builder(env, target='foo.x', source='bar') - assert tgt.scanner != env_scanner, tgt.scanner - assert tgt.src_scanners[''] == env_scanner, tgt.src_scanners + src = tgt.sources[0] + assert tgt.target_scanner != env_scanner, tgt.target_scanner + assert src.source_scanner == env_scanner if __name__ == "__main__": suite = unittest.makeSuite(BuilderTestCase, 'test_') if not unittest.TextTestRunner().run(suite).wasSuccessful(): - sys.exit(1) + sys.exit(1) diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index 1be5e81..0f8425f 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -114,7 +114,7 @@ class FS: def getcwd(self): self.__setTopLevelDir() - return self._cwd + return self._cwd def __checkClass(self, node, klass): if klass == Entry: @@ -132,9 +132,9 @@ class FS: """This method differs from the File and Dir factory methods in one important way: the meaning of the directory parameter. In this method, if directory is None or not supplied, the supplied - name is expected to be an absolute path. If you try to look up a - relative path with directory=None, then an AssertionError will be - raised.""" + name is expected to be an absolute path. If you try to look up a + relative path with directory=None, then an AssertionError will be + raised.""" if not name: # This is a stupid hack to compensate for the fact @@ -280,12 +280,12 @@ class Entry(SCons.Node.Node): """ def __init__(self, name, directory): - """Initialize a generic file system Entry. - - Call the superclass initialization, take care of setting up - our relative and absolute paths, identify our parent - directory, and indicate that this node should use - signatures.""" + """Initialize a generic file system Entry. + + Call the superclass initialization, take care of setting up + our relative and absolute paths, identify our parent + directory, and indicate that this node should use + signatures.""" SCons.Node.Node.__init__(self) self.name = name @@ -302,9 +302,10 @@ class Entry(SCons.Node.Node): self.path_ = self.path self.abspath_ = self.abspath self.dir = directory - self.use_signature = 1 + self.use_signature = 1 self.__doSrcpath(self.duplicate) self.srcpath_ = self.srcpath + self.cwd = None # will hold the SConscript directory for target nodes def get_dir(self): return self.dir @@ -320,7 +321,7 @@ class Entry(SCons.Node.Node): self.srcpath = self.dir.srcpath_ + self.name def __str__(self): - """A FS node's string representation is its path name.""" + """A FS node's string representation is its path name.""" if self.duplicate or self.builder: return self.path else: @@ -374,17 +375,17 @@ class Dir(Entry): def __init__(self, name, directory): Entry.__init__(self, name, directory) - self._morph() + self._morph() def _morph(self): - """Turn a file system node (either a freshly initialized - directory object or a separate Entry object) into a - proper directory object. - - Modify our paths to add the trailing slash that indicates - a directory. Set up this directory's entries and hook it - into the file system tree. Specify that directories (this - node) don't use signatures for currency calculation.""" + """Turn a file system node (either a freshly initialized + directory object or a separate Entry object) into a + proper directory object. + + Modify our paths to add the trailing slash that indicates + a directory. Set up this directory's entries and hook it + into the file system tree. Specify that directories (this + node) don't use signatures for currency calculation.""" self.path_ = self.path + os.sep self.abspath_ = self.abspath + os.sep @@ -423,18 +424,18 @@ class Dir(Entry): else: return self.entries['..'].root() - def children(self, scanner): - #XXX --random: randomize "dependencies?" - keys = filter(lambda k: k != '.' and k != '..', self.entries.keys()) - kids = map(lambda x, s=self: s.entries[x], keys) - def c(one, two): + def all_children(self, scanner): + #XXX --random: randomize "dependencies?" + keys = filter(lambda k: k != '.' and k != '..', self.entries.keys()) + kids = map(lambda x, s=self: s.entries[x], keys) + def c(one, two): if one.abspath < two.abspath: return -1 if one.abspath > two.abspath: return 1 return 0 - kids.sort(c) - return kids + kids.sort(c) + return kids def build(self): """A null "builder" for directories.""" @@ -529,14 +530,12 @@ class File(Entry): .sconsign entry.""" return self.dir.sconsign().get(self.name) - def scan(self, scanner = None): - if not scanner: - scanner = self.scanner - if scanner and not self.scanned.has_key(scanner): - deps = scanner.scan(self, self.env) - self.add_implicit(deps, scanner) - self.scanned[scanner] = 1 - + def get_implicit_deps(self, env, scanner, target): + if scanner: + return scanner.scan(self, env, target) + else: + return [] + def exists(self): if self.duplicate and not self.created: self.created = 1 diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py index 98abcfc..fe9ea61 100644 --- a/src/engine/SCons/Node/FSTests.py +++ b/src/engine/SCons/Node/FSTests.py @@ -48,7 +48,7 @@ class Scanner: global scanner_count scanner_count = scanner_count + 1 self.hash = scanner_count - def scan(self, node, env): + def scan(self, node, env, target): return [node] def __hash__(self): return self.hash @@ -191,7 +191,7 @@ class FSTestCase(unittest.TestCase): up_path = strip_slash(up_path_) name = string.split(abspath, os.sep)[-1] - assert dir.name == name, \ + assert dir.name == name, \ "dir.name %s != expected name %s" % \ (dir.name, name) assert dir.path == path, \ @@ -230,16 +230,16 @@ class FSTestCase(unittest.TestCase): try: f2 = fs.File(string.join(['f1', 'f2'], sep), directory = d1) except TypeError, x: - assert str(x) == ("Tried to lookup File '%s' as a Dir." % - d1_f1), x + assert str(x) == ("Tried to lookup File '%s' as a Dir." % + d1_f1), x except: raise try: dir = fs.Dir(string.join(['d1', 'f1'], sep)) except TypeError, x: - assert str(x) == ("Tried to lookup File '%s' as a Dir." % - d1_f1), x + assert str(x) == ("Tried to lookup File '%s' as a Dir." % + d1_f1), x except: raise @@ -249,7 +249,7 @@ class FSTestCase(unittest.TestCase): assert str(x) == ("Tried to lookup Dir '%s' as a File." % 'd1'), x except: - raise + raise # Test Dir.children() dir = fs.Dir('ddd') @@ -262,9 +262,9 @@ class FSTestCase(unittest.TestCase): kids = map(lambda x: x.path, dir.children(None)) kids.sort() assert kids == [os.path.join('ddd', 'd1'), - os.path.join('ddd', 'f1'), - os.path.join('ddd', 'f2'), - os.path.join('ddd', 'f3')] + os.path.join('ddd', 'f1'), + os.path.join('ddd', 'f2'), + os.path.join('ddd', 'f3')] kids = map(lambda x: x.path_, dir.children(None)) kids.sort() assert kids == [os.path.join('ddd', 'd1', ''), @@ -302,50 +302,50 @@ class FSTestCase(unittest.TestCase): expect = string.replace(expect, '/', os.sep) assert path == expect, "path %s != expected %s" % (path, expect) - e1 = fs.Entry("d1") - assert e1.__class__.__name__ == 'Dir' + e1 = fs.Entry("d1") + assert e1.__class__.__name__ == 'Dir' match(e1.path, "d1") match(e1.path_, "d1/") match(e1.dir.path, ".") - e2 = fs.Entry("d1/f1") - assert e2.__class__.__name__ == 'File' + e2 = fs.Entry("d1/f1") + assert e2.__class__.__name__ == 'File' match(e2.path, "d1/f1") match(e2.path_, "d1/f1") match(e2.dir.path, "d1") - e3 = fs.Entry("e3") - assert e3.__class__.__name__ == 'Entry' + e3 = fs.Entry("e3") + assert e3.__class__.__name__ == 'Entry' match(e3.path, "e3") match(e3.path_, "e3") match(e3.dir.path, ".") - e4 = fs.Entry("d1/e4") - assert e4.__class__.__name__ == 'Entry' + e4 = fs.Entry("d1/e4") + assert e4.__class__.__name__ == 'Entry' match(e4.path, "d1/e4") match(e4.path_, "d1/e4") match(e4.dir.path, "d1") - e5 = fs.Entry("e3/e5") - assert e3.__class__.__name__ == 'Dir' + e5 = fs.Entry("e3/e5") + assert e3.__class__.__name__ == 'Dir' match(e3.path, "e3") match(e3.path_, "e3/") match(e3.dir.path, ".") - assert e5.__class__.__name__ == 'Entry' + assert e5.__class__.__name__ == 'Entry' match(e5.path, "e3/e5") match(e5.path_, "e3/e5") match(e5.dir.path, "e3") - e6 = fs.Dir("d1/e4") - assert e6 is e4 - assert e4.__class__.__name__ == 'Dir' + e6 = fs.Dir("d1/e4") + assert e6 is e4 + assert e4.__class__.__name__ == 'Dir' match(e4.path, "d1/e4") match(e4.path_, "d1/e4/") match(e4.dir.path, "d1") - e7 = fs.File("e3/e5") - assert e7 is e5 - assert e5.__class__.__name__ == 'File' + e7 = fs.File("e3/e5") + assert e7 is e5 + assert e5.__class__.__name__ == 'File' match(e5.path, "e3/e5") match(e5.path_, "e3/e5") match(e5.dir.path, "e3") @@ -383,23 +383,12 @@ class FSTestCase(unittest.TestCase): match(e13.path, "subdir/subdir/e13") # Test scanning - scn1 = Scanner() - f1.scan(scn1) - assert f1.implicit[scn1][0].path_ == os.path.join("d1", "f1") - del f1.implicit[scn1] - f1.scan(scn1) - assert len(f1.implicit) == 0, f1.implicit - del f1.scanned[scn1] - f1.scan(scn1) - assert f1.implicit[scn1][0].path_ == os.path.join("d1", "f1") - - # Test multiple scanners - scn2 = Scanner() - f2 = fs.File("f2") - f2.scan(scn1) - f2.scan(scn2) - match(f2.implicit[scn1][0].path_, 'subdir/f2') - match(f2.implicit[scn2][0].path_, 'subdir/f2') + f1.target_scanner = Scanner() + f1.scan() + assert f1.implicit[0].path_ == os.path.join("d1", "f1") + f1.implicit = [] + f1.scan() + assert f1.implicit[0].path_ == os.path.join("d1", "f1") # Test building a file whose directory is not there yet... f1 = fs.File(test.workpath("foo/bar/baz/ack")) @@ -410,13 +399,13 @@ class FSTestCase(unittest.TestCase): os.chdir('..') - # Test getcwd() + # Test getcwd() fs = SCons.Node.FS.FS() - assert str(fs.getcwd()) == ".", str(fs.getcwd()) - fs.chdir(fs.Dir('subdir')) - assert str(fs.getcwd()) == "subdir", str(fs.getcwd()) - fs.chdir(fs.Dir('../..')) - assert str(fs.getcwd()) == test.workdir, str(fs.getcwd()) + assert str(fs.getcwd()) == ".", str(fs.getcwd()) + fs.chdir(fs.Dir('subdir')) + assert str(fs.getcwd()) == "subdir", str(fs.getcwd()) + fs.chdir(fs.Dir('../..')) + assert str(fs.getcwd()) == test.workdir, str(fs.getcwd()) f1 = fs.File(test.workpath("do_i_exist")) assert not f1.exists() diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py index 16c5548..ef98a41 100644 --- a/src/engine/SCons/Node/NodeTests.py +++ b/src/engine/SCons/Node/NodeTests.py @@ -41,7 +41,7 @@ cycle_detected = None class Builder: def execute(self, **kw): global built_it, built_target, built_source - built_it = 1 + built_it = 1 built_target = kw['target'] built_source = kw['source'] return 0 @@ -87,17 +87,17 @@ class Environment: class NodeTestCase(unittest.TestCase): def test_BuildException(self): - """Test throwing an exception on build failure. - """ - node = SCons.Node.Node() - node.builder_set(FailBuilder()) - node.env_set(Environment()) - try: - node.build() - except SCons.Errors.BuildError: - pass - else: - raise TestFailed, "did not catch expected BuildError" + """Test throwing an exception on build failure. + """ + node = SCons.Node.Node() + node.builder_set(FailBuilder()) + node.env_set(Environment()) + try: + node.build() + except SCons.Errors.BuildError: + pass + else: + raise TestFailed, "did not catch expected BuildError" node = SCons.Node.Node() node.builder_set(ExceptBuilder()) @@ -126,8 +126,8 @@ class NodeTestCase(unittest.TestCase): raise TestFailed, "did not catch expected BuildError" def test_build(self): - """Test building a node - """ + """Test building a node + """ global built_it class MyNode(SCons.Node.Node): @@ -138,18 +138,18 @@ class NodeTestCase(unittest.TestCase): return self.path def prepare(self): self.prepare_count = self.prepare_count+ 1 - # Make sure it doesn't blow up if no builder is set. + # Make sure it doesn't blow up if no builder is set. node = MyNode() - node.build() - assert built_it == None + node.build() + assert built_it == None node = MyNode() - node.builder_set(Builder()) - node.env_set(Environment()) + node.builder_set(Builder()) + node.env_set(Environment()) node.path = "xxx" node.sources = ["yyy", "zzz"] - node.build() - assert built_it + node.build() + assert built_it assert type(built_target) == type(MyNode()), type(built_target) assert str(built_target) == "xxx", str(built_target) assert built_source == ["yyy", "zzz"], built_source @@ -201,13 +201,19 @@ class NodeTestCase(unittest.TestCase): assert str(built_target) == "fff", str(built_target) assert built_source == ["hhh", "iii"], built_source + def test_depends_on(self): + parent = SCons.Node.Node() + child = SCons.Node.Node() + parent.add_dependency([child]) + assert parent.depends_on([child]) + def test_builder_set(self): - """Test setting a Node's Builder - """ - node = SCons.Node.Node() - b = Builder() - node.builder_set(b) - assert node.builder == b + """Test setting a Node's Builder + """ + node = SCons.Node.Node() + b = Builder() + node.builder_set(b) + assert node.builder == b def test_builder_sig_adapter(self): """Test the node's adapter for builder signatures @@ -225,12 +231,12 @@ class NodeTestCase(unittest.TestCase): assert node.current() is None def test_env_set(self): - """Test setting a Node's Environment - """ - node = SCons.Node.Node() - e = Environment() - node.env_set(e) - assert node.env == e + """Test setting a Node's Environment + """ + node = SCons.Node.Node() + e = Environment() + node.env_set(e) + assert node.env == e def test_set_bsig(self): """Test setting a Node's signature @@ -276,16 +282,16 @@ class NodeTestCase(unittest.TestCase): assert node.precious == 7 def test_add_dependency(self): - """Test adding dependencies to a Node's list. - """ - node = SCons.Node.Node() - assert node.depends == [] + """Test adding dependencies to a Node's list. + """ + node = SCons.Node.Node() + assert node.depends == [] zero = SCons.Node.Node() try: - node.add_dependency(zero) - except TypeError: - pass + node.add_dependency(zero) + except TypeError: + pass else: assert 0 @@ -309,42 +315,10 @@ class NodeTestCase(unittest.TestCase): def test_add_source(self): - """Test adding sources to a Node's list. - """ - node = SCons.Node.Node() - assert node.sources == [] - - zero = SCons.Node.Node() - try: - node.add_source(zero) - except TypeError: - pass - else: - assert 0 - - one = SCons.Node.Node() - two = SCons.Node.Node() - three = SCons.Node.Node() - four = SCons.Node.Node() - - node.add_source([one]) - assert node.sources == [one] - node.add_source([two, three]) - assert node.sources == [one, two, three] - node.add_source([three, four, one]) - assert node.sources == [one, two, three, four] - - assert zero.get_parents() == [] - assert one.get_parents() == [node] - assert two.get_parents() == [node] - assert three.get_parents() == [node] - assert four.get_parents() == [node] - - def test_add_implicit(self): - """Test adding implicit (scanned) dependencies to a Node's list. + """Test adding sources to a Node's list. """ node = SCons.Node.Node() - assert node.implicit == {} + assert node.sources == [] zero = SCons.Node.Node() try: @@ -359,12 +333,12 @@ class NodeTestCase(unittest.TestCase): three = SCons.Node.Node() four = SCons.Node.Node() - node.add_implicit([one], 1) - assert node.implicit[1] == [one] - node.add_implicit([two, three], 1) - assert node.implicit[1] == [one, two, three] - node.add_implicit([three, four, one], 1) - assert node.implicit[1] == [one, two, three, four] + node.add_source([one]) + assert node.sources == [one] + node.add_source([two, three]) + assert node.sources == [one, two, three] + node.add_source([three, four, one]) + assert node.sources == [one, two, three, four] assert zero.get_parents() == [] assert one.get_parents() == [node] @@ -372,15 +346,6 @@ class NodeTestCase(unittest.TestCase): assert three.get_parents() == [node] assert four.get_parents() == [node] - node.add_implicit([one], 2) - node.add_implicit([two, three], 3) - node.add_implicit([three, four, one], 4) - - assert node.implicit[1] == [one, two, three, four] - assert node.implicit[2] == [one] - assert node.implicit[3] == [two, three] - assert node.implicit[4] == [three, four, one] - def test_add_ignore(self): """Test adding files whose dependencies should be ignored. """ @@ -419,42 +384,9 @@ class NodeTestCase(unittest.TestCase): pass ds=DummyScanner() node = SCons.Node.Node() - assert node.scanner == None, node.scanner - node.scanner_set(ds) - assert node.scanner == ds, node.scanner - node.scan(ds) - assert node.scanned[ds] == 1, node.scanned - - def test_src_scanner_set(self): - """Test setting source-file Scanners""" - class DummyScanner: - pass - ds1=DummyScanner() - ds2=DummyScanner() - node = SCons.Node.Node() - assert node.src_scanners == {}, node.src_scanners - node.src_scanner_set('a', ds1) - assert node.src_scanners['a'] == ds1, node.src_scanners - node.src_scanner_set('b', ds2) - assert node.src_scanners['b'] == ds2, node.src_scanners - - def test_src_scanner_set(self): - """Test setting source-file Scanners""" - class DummyScanner: - pass - ds1=DummyScanner() - ds2=DummyScanner() - node = SCons.Node.Node() - node.src_scanner_set('a', ds1) - node.src_scanner_set('b', ds2) - s = node.src_scanner_get(None) - assert s == None, s - s = node.src_scanner_get('a') - assert s == ds1, s - s = node.src_scanner_get('b') - assert s == ds2, s - s = node.src_scanner_get('c') - assert s == None, s + assert node.target_scanner == None, node.target_scanner + node.target_scanner = ds + node.scan() def test_scanner_key(self): """Test that a scanner_key() method exists""" @@ -463,7 +395,7 @@ class NodeTestCase(unittest.TestCase): def test_children(self): """Test fetching the non-ignored "children" of a Node. """ - node = SCons.Node.Node() + node = SCons.Node.Node() n1 = SCons.Node.Node() n2 = SCons.Node.Node() n3 = SCons.Node.Node() @@ -479,28 +411,16 @@ class NodeTestCase(unittest.TestCase): node.add_source([n1, n2, n3]) node.add_dependency([n4, n5, n6]) - node.add_implicit([n7, n8, n9], 'key1') - node.add_implicit([n10, n11, n12], 'key2') + node._add_child(node.implicit, [n7, n8, n9]) + node._add_child(node.implicit, [n10, n11, n12]) node.add_ignore([n2, n5, n8, n11]) - kids = node.children(None) + kids = node.children() for kid in [n1, n3, n4, n6, n7, n9, n10, n12]: assert kid in kids, kid for kid in [n2, n5, n8, n11]: assert not kid in kids, kid - kids = node.children('key1') - for kid in [n1, n3, n4, n6, n7, n9]: - assert kid in kids, kid - for kid in [n2, n5, n8, n10, n11, n12]: - assert not kid in kids, kid - - kids = node.children('key2') - for kid in [n1, n3, n4, n6, n10, n12]: - assert kid in kids, kid - for kid in [n2, n5, n7, n8, n9, n11]: - assert not kid in kids, kid - def test_all_children(self): """Test fetching all the "children" of a Node. """ @@ -520,29 +440,17 @@ class NodeTestCase(unittest.TestCase): node.add_source([n1, n2, n3]) node.add_dependency([n4, n5, n6]) - node.add_implicit([n7, n8, n9], 'key1') - node.add_implicit([n10, n11, n12], 'key2') + node._add_child(node.implicit, [n7, n8, n9]) + node._add_child(node.implicit, [n10, n11, n12]) node.add_ignore([n2, n5, n8, n11]) - kids = node.all_children(None) + kids = node.all_children() for kid in [n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12]: assert kid in kids - kids = node.all_children('key1') - for kid in [n1, n2, n3, n4, n5, n6, n7, n8, n9]: - assert kid in kids - for kid in [n10, n11, n12]: - assert not kid in kids - - kids = node.all_children('key2') - for kid in [n1, n2, n3, n4, n5, n6, n10, n11, n12]: - assert kid in kids - for kid in [n7, n8, n9]: - assert not kid in kids - def test_state(self): - """Test setting and getting the state of a node - """ + """Test setting and getting the state of a node + """ node = SCons.Node.Node() assert node.get_state() == None node.set_state(SCons.Node.executing) @@ -553,51 +461,51 @@ class NodeTestCase(unittest.TestCase): assert SCons.Node.executed < SCons.Node.failed def test_walker(self): - """Test walking a Node tree. - """ + """Test walking a Node tree. + """ - class MyNode(SCons.Node.Node): - def __init__(self, name): - SCons.Node.Node.__init__(self) - self.name = name + class MyNode(SCons.Node.Node): + def __init__(self, name): + SCons.Node.Node.__init__(self) + self.name = name - n1 = MyNode("n1") + n1 = MyNode("n1") - nw = SCons.Node.Walker(n1) + nw = SCons.Node.Walker(n1) assert not nw.is_done() - assert nw.next().name == "n1" + assert nw.next().name == "n1" assert nw.is_done() - assert nw.next() == None - - n2 = MyNode("n2") - n3 = MyNode("n3") - n1.add_source([n2, n3]) - - nw = SCons.Node.Walker(n1) - assert nw.next().name == "n2" - assert nw.next().name == "n3" - assert nw.next().name == "n1" - assert nw.next() == None - - n4 = MyNode("n4") - n5 = MyNode("n5") - n6 = MyNode("n6") - n7 = MyNode("n7") - n2.add_source([n4, n5]) - n3.add_dependency([n6, n7]) - - nw = SCons.Node.Walker(n1) - assert nw.next().name == "n4" - assert nw.next().name == "n5" + assert nw.next() == None + + n2 = MyNode("n2") + n3 = MyNode("n3") + n1.add_source([n2, n3]) + + nw = SCons.Node.Walker(n1) + assert nw.next().name == "n2" + assert nw.next().name == "n3" + assert nw.next().name == "n1" + assert nw.next() == None + + n4 = MyNode("n4") + n5 = MyNode("n5") + n6 = MyNode("n6") + n7 = MyNode("n7") + n2.add_source([n4, n5]) + n3.add_dependency([n6, n7]) + + nw = SCons.Node.Walker(n1) + assert nw.next().name == "n4" + assert nw.next().name == "n5" assert nw.history.has_key(n2) - assert nw.next().name == "n2" - assert nw.next().name == "n6" - assert nw.next().name == "n7" + assert nw.next().name == "n2" + assert nw.next().name == "n6" + assert nw.next().name == "n7" assert nw.history.has_key(n3) - assert nw.next().name == "n3" + assert nw.next().name == "n3" assert nw.history.has_key(n1) - assert nw.next().name == "n1" - assert nw.next() == None + assert nw.next().name == "n1" + assert nw.next() == None n8 = MyNode("n8") n8.add_dependency([n3]) @@ -621,69 +529,6 @@ class NodeTestCase(unittest.TestCase): n = nw.next() assert nw.next() == None - def test_children_are_executed(self): - n1 = SCons.Node.Node() - n2 = SCons.Node.Node() - n3 = SCons.Node.Node() - n4 = SCons.Node.Node() - - n4.add_source([n3]) - n3.add_source([n1, n2]) - - assert not n4.children_are_executed(None) - assert not n3.children_are_executed(None) - assert n2.children_are_executed(None) - assert n1.children_are_executed(None) - - n1.set_state(SCons.Node.executed) - assert not n4.children_are_executed(None) - assert not n3.children_are_executed(None) - assert n2.children_are_executed(None) - assert n1.children_are_executed(None) - - n2.set_state(SCons.Node.executed) - assert not n4.children_are_executed(None) - assert n3.children_are_executed(None) - assert n2.children_are_executed(None) - assert n1.children_are_executed(None) - - n3.set_state(SCons.Node.executed) - assert n4.children_are_executed(None) - assert n3.children_are_executed(None) - assert n2.children_are_executed(None) - assert n1.children_are_executed(None) - - def test_rescan(self): - """Test that built node implicit dependencies are cleared - to be rescanned.""" - class DummyScanner: - pass - - class TestNode(SCons.Node.Node): - def scan(self, scanner): - if not self.scanned.has_key(scanner): - n=SCons.Node.Node() - n.scanner_set(scanner) - self.add_implicit([ n ], scanner) - self.scanned[scanner] = 1 - tn=TestNode() - tn.builder_set(Builder()) - tn.env_set(Environment()) - ds = DummyScanner() - tn.scan(ds) - map(lambda x: x.scan(), tn.depends) - assert tn.scanned[ds] - assert len(tn.implicit[ds]) == 1, tn.implicit - tn.scan(ds) - assert tn.scanned[ds] - assert len(tn.implicit[ds]) == 1, tn.implicit - tn.build() - assert not tn.scanned.has_key(ds) - assert len(tn.implicit[ds]) == 1, tn.implicit - tn.scan(ds) - assert tn.scanned[ds] - assert len(tn.implicit[ds]) == 2, tn.implicit - def test_arg2nodes(self): """Test the arg2nodes function.""" dict = {} @@ -779,4 +624,4 @@ class NodeTestCase(unittest.TestCase): if __name__ == "__main__": suite = unittest.makeSuite(NodeTestCase, 'test_') if not unittest.TextTestRunner().run(suite).wasSuccessful(): - sys.exit(1) + sys.exit(1) diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index 98b2d50..db11e55 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -51,6 +51,7 @@ executing = 2 up_to_date = 3 executed = 4 failed = 5 +stack = 6 # nodes that are in the current Taskmaster execution stack class Node: """The base Node class, for entities that we know how to @@ -60,14 +61,13 @@ class Node: def __init__(self): self.sources = [] # source files used to build node self.depends = [] # explicit dependencies (from Depends) - self.implicit = {} # implicit (scanned) dependencies - self.ignore = [] # dependencies to ignore + self.implicit = [] # implicit (scanned) dependencies + self.ignore = [] # dependencies to ignore self.parents = {} self.wkids = None # Kids yet to walk, when it's an array self.builder = None - self.scanner = None # explicit scanner from this node's Builder - self.scanned = {} # cached scanned values - self.src_scanners = {} # scanners for this node's source files + self.source_scanner = None # implicit scanner from scanner map + self.target_scanner = None # explicit scanner from this node's Builder self.env = None self.state = None self.bsig = None @@ -75,11 +75,14 @@ class Node: self.use_signature = 1 self.precious = None self.found_includes = {} + self.includes = None def build(self): """Actually build the node. Return the status from the build.""" - if not self.builder: - return None + # This method is called from multiple threads in a parallel build, + # so only do thread safe stuff here. Do thread unsafe stuff in built(). + if not self.builder: + return None try: # If this Builder instance has already been called, # there will already be an associated status. @@ -103,16 +106,33 @@ class Node: if stat: raise BuildError(node = self, errstr = "Error %d" % stat) - self.found_includes = {} - - # If we successfully build a node, then we need to rescan for - # implicit dependencies, since it might have changed on us. - self.scanned = {} - return stat + def built(self): + """Called just after this node is sucessfully built.""" + # Clear out the implicit dependency caches: + # XXX this really should somehow be made more general and put + # under the control of the scanners. + if self.source_scanner: + self.found_includes = {} + self.includes = None + + def get_parents(node, parent): return node.get_parents() + def clear_cache(node, parent): + node.implicit = [] + w = Walker(self, get_parents, ignore_cycle, clear_cache) + while w.next(): pass + + def depends_on(self, nodes): + """Does this node depend on any of 'nodes'?""" + for node in nodes: + if node in self.children(): + return 1 + + return 0 + def builder_set(self, builder): - self.builder = builder + self.builder = builder def builder_sig_adapter(self): """Create an adapter for calculating a builder's signature. @@ -136,19 +156,21 @@ class Node: return self.node.builder.get_contents(env = dict) return Adapter(self) - def scanner_set(self, scanner): - self.scanner = scanner - - def src_scanner_set(self, key, scanner): - self.src_scanners[key] = scanner - - def src_scanner_get(self, key): - return self.src_scanners.get(key, None) - - def scan(self, scanner = None): - if not scanner: - scanner = self.scanner - self.scanned[scanner] = 1 + def get_implicit_deps(self, env, scanner, target): + """Return a list of implicit dependencies for this node""" + return [] + + def scan(self): + """Scan this node's dependents for implicit dependencies.""" + # Don't bother scanning non-derived files, because we don't + # care what their dependencies are. + # Don't scan again, if we already have scanned. + if self.builder and not self.implicit: + for child in self.children(scan=0): + self._add_child(self.implicit, child.get_implicit_deps(self.env, child.source_scanner, self)) + + # scan this node itself for implicit dependencies + self._add_child(self.implicit, self.get_implicit_deps(self.env, self.target_scanner, self)) def scanner_key(self): return None @@ -156,7 +178,7 @@ class Node: def env_set(self, env, safe=0): if safe and self.env: return - self.env = env + self.env = env def get_bsig(self): """Get the node's build signature (based on the signatures @@ -190,7 +212,7 @@ class Node: pass def add_dependency(self, depend): - """Adds dependencies. The depend argument must be a list.""" + """Adds dependencies. The depend argument must be a list.""" self._add_child(self.depends, depend) def add_ignore(self, depend): @@ -198,23 +220,16 @@ class Node: self._add_child(self.ignore, depend) def add_source(self, source): - """Adds sources. The source argument must be a list.""" + """Adds sources. The source argument must be a list.""" self._add_child(self.sources, source) - def add_implicit(self, implicit, key): - """Adds implicit (scanned) dependencies. The implicit - argument must be a list.""" - if not self.implicit.has_key(key): - self.implicit[key] = [] - self._add_child(self.implicit[key], implicit) - def _add_child(self, collection, child): """Adds 'child' to 'collection'. The 'child' argument must be a list""" if type(child) is not type([]): raise TypeError("child must be a list") - child = filter(lambda x, s=collection: x not in s, child) - if child: - collection.extend(child) + child = filter(lambda x, s=collection: x not in s, child) + if child: + collection.extend(child) for c in child: c.parents[self] = 1 @@ -224,22 +239,18 @@ class Node: if self.wkids != None: self.wkids.append(wkid) - def children(self, scanner): + def children(self, scan=1): """Return a list of the node's direct children, minus those that are ignored by this node.""" return filter(lambda x, i=self.ignore: x not in i, - self.all_children(scanner)) + self.all_children(scan)) - def all_children(self, scanner): + def all_children(self, scan=1): """Return a list of all the node's direct children.""" #XXX Need to remove duplicates from this - if not self.implicit.has_key(scanner): - self.scan(scanner) - if scanner: - implicit = self.implicit[scanner] - else: - implicit = reduce(lambda x, y: x + y, self.implicit.values(), []) - return self.sources + self.depends + implicit + if scan and not self.implicit: + self.scan() + return self.sources + self.depends + self.implicit def get_parents(self): return self.parents.keys() @@ -253,14 +264,7 @@ class Node: def current(self): return None - def children_are_executed(self, scanner): - return reduce(lambda x,y: ((y.get_state() == executed - or y.get_state() == up_to_date) - and x), - self.children(scanner), - 1) - -def get_children(node, parent): return node.children(None) +def get_children(node, parent): return node.children() def ignore_cycle(node, stack): pass def do_nothing(node, parent): pass @@ -290,13 +294,13 @@ class Walker: self.history[node] = None def next(self): - """Return the next node for this walk of the tree. + """Return the next node for this walk of the tree. - This function is intentionally iterative, not recursive, - to sidestep any issues of stack size limitations. - """ + This function is intentionally iterative, not recursive, + to sidestep any issues of stack size limitations. + """ - while self.stack: + while self.stack: if self.stack[-1].wkids: node = self.stack[-1].wkids.pop(0) if not self.stack[-1].wkids: diff --git a/src/engine/SCons/Scanner/C.py b/src/engine/SCons/Scanner/C.py index 0a2654e..c81417a 100644 --- a/src/engine/SCons/Scanner/C.py +++ b/src/engine/SCons/Scanner/C.py @@ -41,45 +41,16 @@ import SCons.Util include_re = re.compile('^[ \t]*#[ \t]*include[ \t]+(<|")([\\w./\\\\]+)(>|")', re.M) -include_cache = {} - def CScan(fs = SCons.Node.FS.default_fs): """Return a prototype Scanner instance for scanning source files that use the C pre-processor""" - cs = CScanner(scan, "CScan", [fs, ()], - [".c", ".C", ".cxx", ".cpp", ".c++", ".cc", - ".h", ".H", ".hxx", ".hpp", ".hh", - ".F", ".fpp", ".FPP"]) - cs.fs = fs + cs = SCons.Scanner.Recursive(scan, "CScan", fs, + [".c", ".C", ".cxx", ".cpp", ".c++", ".cc", + ".h", ".H", ".hxx", ".hpp", ".hh", + ".F", ".fpp", ".FPP"]) return cs -class CScanner(SCons.Scanner.Recursive): - def __init__(self, *args, **kw): - apply(SCons.Scanner.Recursive.__init__, (self,) + args, kw) - self.hash = None - self.pathscanners = {} - - def instance(self, env): - """ - Return a unique instance of a C scanner object for a - given environment. - """ - try: - dirs = tuple(SCons.Node.arg2nodes(env.Dictionary('CPPPATH'), - self.fs.Dir)) - except: - dirs = () - if not self.pathscanners.has_key(dirs): - clone = copy.copy(self) - clone.hash = dirs - clone.argument = [self.fs, dirs] # XXX reaching into object - self.pathscanners[dirs] = clone - return self.pathscanners[dirs] - - def __hash__(self): - return hash(self.hash) - -def scan(node, env, args = [SCons.Node.FS.default_fs, ()]): +def scan(node, env, target, fs = SCons.Node.FS.default_fs): """ scan(node, Environment) -> [node] @@ -100,7 +71,21 @@ def scan(node, env, args = [SCons.Node.FS.default_fs, ()]): dependencies. """ - fs, cpppath = args + # This function caches various information in node and target: + # target.cpppath - env['CPPPATH'] converted to nodes + # node.found_includes - include files found by previous call to scan, + # keyed on cpppath + # node.includes - the result of include_re.findall() + + if not hasattr(target, 'cpppath'): + def Dir(x, dir=target.cwd, fs=fs): return fs.Dir(x,dir) + try: + target.cpppath = tuple(SCons.Node.arg2nodes(env['CPPPATH'],Dir)) + except KeyError: + target.cpppath = () + + cpppath = target.cpppath + nodes = [] try: @@ -109,7 +94,7 @@ def scan(node, env, args = [SCons.Node.FS.default_fs, ()]): if node.exists(): # cache the includes list in node so we only scan it once: - if hasattr(node, 'includes'): + if node.includes != None: includes = node.includes else: includes = include_re.findall(node.get_contents()) diff --git a/src/engine/SCons/Scanner/CTests.py b/src/engine/SCons/Scanner/CTests.py index 684df99..f99af18 100644 --- a/src/engine/SCons/Scanner/CTests.py +++ b/src/engine/SCons/Scanner/CTests.py @@ -119,6 +119,10 @@ test.write('subdir/include/fb.h', "\n") # define some helpers: +class DummyTarget: + def __init__(self, cwd=None): + self.cwd = cwd + class DummyEnvironment: def __init__(self, listCppPath): self.path = listCppPath @@ -131,6 +135,15 @@ class DummyEnvironment: else: raise KeyError, "Dummy environment only has CPPPATH attribute." + def __getitem__(self,key): + return self.Dictionary()[key] + + def __setitem__(self,key,value): + self.Dictionary()[key] = value + + def __delitem__(self,key): + del self.Dictionary()[key] + def deps_match(self, deps, headers): scanned = map(os.path.normpath, map(str, deps)) expect = map(os.path.normpath, headers) @@ -145,15 +158,15 @@ class CScannerTestCase1(unittest.TestCase): def runTest(self): env = DummyEnvironment([]) s = SCons.Scanner.C.CScan() - deps = s.instance(env).scan(make_node('f1.cpp'), env) - headers = ['f1.h', 'f2.h', 'fi.h'] + deps = s.scan(make_node('f1.cpp'), env, DummyTarget()) + headers = ['f1.h', 'f2.h', 'fi.h'] deps_match(self, deps, map(test.workpath, headers)) class CScannerTestCase2(unittest.TestCase): def runTest(self): env = DummyEnvironment([test.workpath("d1")]) s = SCons.Scanner.C.CScan() - deps = s.instance(env).scan(make_node('f1.cpp'), env) + deps = s.scan(make_node('f1.cpp'), env, DummyTarget()) headers = ['d1/f2.h', 'f1.h'] deps_match(self, deps, map(test.workpath, headers)) @@ -161,7 +174,7 @@ class CScannerTestCase3(unittest.TestCase): def runTest(self): env = DummyEnvironment([test.workpath("d1")]) s = SCons.Scanner.C.CScan() - deps = s.instance(env).scan(make_node('f2.cpp'), env) + deps = s.scan(make_node('f2.cpp'), env, DummyTarget()) headers = ['d1/d2/f1.h', 'd1/f1.h', 'f1.h'] deps_match(self, deps, map(test.workpath, headers)) @@ -169,7 +182,7 @@ class CScannerTestCase4(unittest.TestCase): def runTest(self): env = DummyEnvironment([test.workpath("d1"), test.workpath("d1/d2")]) s = SCons.Scanner.C.CScan() - deps = s.instance(env).scan(make_node('f2.cpp'), env) + deps = s.scan(make_node('f2.cpp'), env, DummyTarget()) headers = ['d1/d2/f1.h', 'd1/d2/f4.h', 'd1/f1.h', 'f1.h'] deps_match(self, deps, map(test.workpath, headers)) @@ -177,7 +190,7 @@ class CScannerTestCase5(unittest.TestCase): def runTest(self): env = DummyEnvironment([]) s = SCons.Scanner.C.CScan() - deps = s.instance(env).scan(make_node('f3.cpp'), env) + deps = s.scan(make_node('f3.cpp'), env, DummyTarget()) # Make sure exists() gets called on the file node being # scanned, essential for cooperation with BuildDir functionality. @@ -193,35 +206,23 @@ class CScannerTestCase6(unittest.TestCase): env2 = DummyEnvironment([test.workpath("d1/d2")]) env3 = DummyEnvironment([test.workpath("d1/../d1")]) s = SCons.Scanner.C.CScan() - s1 = s.instance(env1) - s2 = s.instance(env2) - s3 = s.instance(env3) - assert not s1 is s2 - assert s1 is s3 - deps1 = s1.scan(make_node('f1.cpp'), None) - deps2 = s2.scan(make_node('f1.cpp'), None) + deps1 = s.scan(make_node('f1.cpp'), env1, DummyTarget()) + deps2 = s.scan(make_node('f1.cpp'), env2, DummyTarget()) headers1 = ['d1/f2.h', 'f1.h'] headers2 = ['d1/d2/f2.h', 'f1.h'] deps_match(self, deps1, map(test.workpath, headers1)) deps_match(self, deps2, map(test.workpath, headers2)) -class CScannerTestCase7(unittest.TestCase): - def runTest(self): - s = SCons.Scanner.C.CScan() - s1 = s.instance(DummyEnvironment([test.workpath("d1")])) - s2 = s.instance(DummyEnvironment([test.workpath("d1/../d1")])) - dict = {} - dict[s1] = 777 - assert dict[s2] == 777 - class CScannerTestCase8(unittest.TestCase): def runTest(self): fs = SCons.Node.FS.FS(test.workpath('')) env = DummyEnvironment(["include"]) s = SCons.Scanner.C.CScan(fs = fs) - deps1 = s.instance(env).scan(fs.File('fa.cpp'), None) + deps1 = s.scan(fs.File('fa.cpp'), env, DummyTarget()) fs.chdir(fs.Dir('subdir')) - deps2 = s.instance(env).scan(fs.File('#fa.cpp'), None) + target = DummyTarget(fs.getcwd()) + fs.chdir(fs.Dir('..')) + deps2 = s.scan(fs.File('#fa.cpp'), env, target) headers1 = ['include/fa.h', 'include/fb.h'] headers2 = ['subdir/include/fa.h', 'subdir/include/fb.h'] deps_match(self, deps1, headers1) @@ -233,7 +234,7 @@ class CScannerTestCase9(unittest.TestCase): fs = SCons.Node.FS.FS(test.workpath('')) s = SCons.Scanner.C.CScan(fs=fs) env = DummyEnvironment([]) - deps = s.instance(env).scan(fs.File('fa.cpp'), None) + deps = s.scan(fs.File('fa.cpp'), env, DummyTarget()) deps_match(self, deps, [ 'fa.h' ]) test.unlink('fa.h') @@ -244,7 +245,7 @@ class CScannerTestCase10(unittest.TestCase): s = SCons.Scanner.C.CScan(fs=fs) env = DummyEnvironment([]) test.write('include/fa.cpp', test.read('fa.cpp')) - deps = s.instance(env).scan(fs.File('#include/fa.cpp'), None) + deps = s.scan(fs.File('#include/fa.cpp'), env, DummyTarget()) deps_match(self, deps, [ 'include/fa.h', 'include/fb.h' ]) test.unlink('include/fa.cpp') @@ -256,7 +257,6 @@ def suite(): suite.addTest(CScannerTestCase4()) suite.addTest(CScannerTestCase5()) suite.addTest(CScannerTestCase6()) - suite.addTest(CScannerTestCase7()) suite.addTest(CScannerTestCase8()) suite.addTest(CScannerTestCase9()) suite.addTest(CScannerTestCase10()) diff --git a/src/engine/SCons/Scanner/Prog.py b/src/engine/SCons/Scanner/Prog.py index 9ecc37b..76567ab 100644 --- a/src/engine/SCons/Scanner/Prog.py +++ b/src/engine/SCons/Scanner/Prog.py @@ -31,74 +31,49 @@ import SCons.Node.FS import SCons.Scanner import SCons.Util -class NullProgScanner: - """A do-nothing ProgScanner for Environments that have no LIBS.""" - def scan(node, env, args = []): - return [] - -null_scanner = NullProgScanner() - def ProgScan(fs = SCons.Node.FS.default_fs): """Return a prototype Scanner instance for scanning executable files for static-lib dependencies""" - ps = ProgScanner(scan, "ProgScan") - ps.fs = fs + ps = SCons.Scanner.Base(scan, "ProgScan", fs) return ps -class ProgScanner(SCons.Scanner.Base): - def __init__(self, *args, **kw): - apply(SCons.Scanner.Base.__init__, (self,) + args, kw) - self.hash = None - self.pathscanners = {} - - def instance(self, env): - """ - Return a unique instance of a Prog scanner object for a - given environment. - """ - try: - libs = env.Dictionary('LIBS') - except KeyError: - # There are no LIBS in this environment, so just return the - # fake "scanner" instance that always returns a null list. - return null_scanner - if SCons.Util.is_String(libs): - libs = string.split(libs) +def scan(node, env, target, fs): + """ + This scanner scans program files for static-library + dependencies. It will search the LIBPATH environment variable + for libraries specified in the LIBS variable, returning any + files it finds as dependencies. + """ - try: - dirs = tuple(SCons.Node.arg2nodes(env.Dictionary('LIBPATH'), - self.fs.Dir)) - except: - dirs = () + # This function caches information in target: + # target.libpath - env['LIBPATH'] converted to nodes + if not hasattr(target, 'libpath'): + def Dir(x, dir=target.cwd, fs=fs): return fs.Dir(x,dir) try: - prefix = env.Dictionary('LIBPREFIX') + target.libpath = tuple(SCons.Node.arg2nodes(env['LIBPATH'],Dir)) except KeyError: - prefix = '' + target.libpath = () + + libpath = target.libpath - try: - suffix = env.Dictionary('LIBSUFFIX') - except KeyError: - suffix = '' + try: + libs = env.Dictionary('LIBS') + except KeyError: + # There are no LIBS in this environment, so just return a null list: + return [] + if SCons.Util.is_String(libs): + libs = string.split(libs) - key = (dirs, tuple(libs), prefix, suffix) - if not self.pathscanners.has_key(key): - clone = copy.copy(self) - clone.hash = key - clone.argument = [self.fs, dirs, libs, prefix, suffix] # XXX reaching into object - self.pathscanners[key] = clone - return self.pathscanners[key] + try: + prefix = env.Dictionary('LIBPREFIX') + except KeyError: + prefix = '' - def __hash__(self): - return hash(self.hash) + try: + suffix = env.Dictionary('LIBSUFFIX') + except KeyError: + suffix = '' -def scan(node, env, args = [SCons.Node.FS.default_fs, (), [], '', '']): - """ - This scanner scans program files for static-library - dependencies. It will search the LIBPATH environment variable - for libraries specified in the LIBS variable, returning any - files it finds as dependencies. - """ - fs, libpath, libs, prefix, suffix = args libs = map(lambda x, s=suffix, p=prefix: p + x + s, libs) return SCons.Node.FS.find_files(libs, libpath, fs.File) diff --git a/src/engine/SCons/Scanner/ProgTests.py b/src/engine/SCons/Scanner/ProgTests.py index b59e554..ea3d00d 100644 --- a/src/engine/SCons/Scanner/ProgTests.py +++ b/src/engine/SCons/Scanner/ProgTests.py @@ -42,6 +42,10 @@ for h in libs: # define some helpers: +class DummyTarget: + def __init__(self, cwd=None): + self.cwd = cwd + class DummyEnvironment: def __init__(self, **kw): self._dict = kw @@ -54,6 +58,14 @@ class DummyEnvironment: return self._dict[args[0]] else: return map(lambda x, s=self: s._dict[x], args) + def __getitem__(self,key): + return self.Dictionary()[key] + + def __setitem__(self,key,value): + self.Dictionary()[key] = value + + def __delitem__(self,key): + del self.Dictionary()[key] def deps_match(deps, libs): deps=map(str, deps) @@ -70,7 +82,7 @@ class ProgScanTestCase1(unittest.TestCase): env = DummyEnvironment(LIBPATH=[ test.workpath("") ], LIBS=[ 'l1', 'l2', 'l3' ]) s = SCons.Scanner.Prog.ProgScan() - deps = s.instance(env).scan('dummy', env) + deps = s.scan('dummy', env, DummyTarget()) assert deps_match(deps, ['l1.lib']), map(str, deps) class ProgScanTestCase2(unittest.TestCase): @@ -79,7 +91,7 @@ class ProgScanTestCase2(unittest.TestCase): ["", "d1", "d1/d2" ]), LIBS=[ 'l1', 'l2', 'l3' ]) s = SCons.Scanner.Prog.ProgScan() - deps = s.instance(env).scan('dummy', env) + deps = s.scan('dummy', env, DummyTarget()) assert deps_match(deps, ['l1.lib', 'd1/l2.lib', 'd1/d2/l3.lib' ]), map(str, deps) class ProgScanTestCase3(unittest.TestCase): @@ -88,7 +100,7 @@ class ProgScanTestCase3(unittest.TestCase): test.workpath("d1"), LIBS='l2 l3') s = SCons.Scanner.Prog.ProgScan() - deps = s.instance(env).scan('dummy', env) + deps = s.scan('dummy', env, DummyTarget()) assert deps_match(deps, ['d1/l2.lib', 'd1/d2/l3.lib']), map(str, deps) def suite(): @@ -104,7 +116,7 @@ def suite(): test.workpath("d1"), LIBS=u'l2 l3') s = SCons.Scanner.Prog.ProgScan() - deps = s.instance(env).scan('dummy', env) + deps = s.scan('dummy', env, DummyTarget()) assert deps_match(deps, ['d1/l2.lib', 'd1/d2/l3.lib']), map(str, deps) suite.addTest(ProgScanTestCase4()) \n""" diff --git a/src/engine/SCons/Scanner/ScannerTests.py b/src/engine/SCons/Scanner/ScannerTests.py index 76df18e..fce4078 100644 --- a/src/engine/SCons/Scanner/ScannerTests.py +++ b/src/engine/SCons/Scanner/ScannerTests.py @@ -27,11 +27,16 @@ import unittest import SCons.Scanner import sys +class DummyTarget: + cwd = None + + class ScannerTestBase: - def func(self, filename, env, *args): + def func(self, filename, env, target, *args): self.filename = filename self.env = env + self.target = target if len(args) > 0: self.arg = args[0] @@ -41,7 +46,7 @@ class ScannerTestBase: def test(self, scanner, env, filename, deps, *args): self.deps = deps - scanned = scanner.scan(filename, env) + scanned = scanner.scan(filename, env, DummyTarget()) scanned_strs = map(lambda x: str(x), scanned) self.failUnless(self.filename == filename, "the filename was passed incorrectly") @@ -67,10 +72,9 @@ class ScannerPositionalTestCase(ScannerTestBase, unittest.TestCase): env.VARIABLE = "var1" self.test(s, env, 'f1.cpp', ['f1.h', 'f1.hpp']) - env = DummyEnvironment() - env.VARIABLE = "i1" - i = s.instance(env) - self.test(i, env, 'i1.cpp', ['i1.h', 'i1.hpp']) + env = DummyEnvironment() + env.VARIABLE = "i1" + self.test(s, env, 'i1.cpp', ['i1.h', 'i1.hpp']) class ScannerKeywordTestCase(ScannerTestBase, unittest.TestCase): "Test the Scanner.Base class using the keyword argument" @@ -80,10 +84,9 @@ class ScannerKeywordTestCase(ScannerTestBase, unittest.TestCase): env.VARIABLE = "var2" self.test(s, env, 'f2.cpp', ['f2.h', 'f2.hpp']) - env = DummyEnvironment() - env.VARIABLE = "i2" - i = s.instance(env) - self.test(i, env, 'i2.cpp', ['i2.h', 'i2.hpp']) + env = DummyEnvironment() + env.VARIABLE = "i2" + self.test(s, env, 'i2.cpp', ['i2.h', 'i2.hpp']) class ScannerPositionalArgumentTestCase(ScannerTestBase, unittest.TestCase): "Test the Scanner.Base class using both position and optional arguments" @@ -94,10 +97,9 @@ class ScannerPositionalArgumentTestCase(ScannerTestBase, unittest.TestCase): env.VARIABLE = "var3" self.test(s, env, 'f3.cpp', ['f3.h', 'f3.hpp'], arg) - env = DummyEnvironment() - env.VARIABLE = "i3" - i = s.instance(env) - self.test(i, env, 'i3.cpp', ['i3.h', 'i3.hpp'], arg) + env = DummyEnvironment() + env.VARIABLE = "i3" + self.test(s, env, 'i3.cpp', ['i3.h', 'i3.hpp'], arg) class ScannerKeywordArgumentTestCase(ScannerTestBase, unittest.TestCase): "Test the Scanner.Base class using both keyword and optional arguments" @@ -109,10 +111,9 @@ class ScannerKeywordArgumentTestCase(ScannerTestBase, unittest.TestCase): env.VARIABLE = "var4" self.test(s, env, 'f4.cpp', ['f4.h', 'f4.hpp'], arg) - env = DummyEnvironment() - env.VARIABLE = "i4" - i = s.instance(env) - self.test(i, env, 'i4.cpp', ['i4.h', 'i4.hpp'], arg) + env = DummyEnvironment() + env.VARIABLE = "i4" + self.test(s, env, 'i4.cpp', ['i4.h', 'i4.hpp'], arg) class ScannerHashTestCase(ScannerTestBase, unittest.TestCase): "Test the Scanner.Base class __hash__() method" diff --git a/src/engine/SCons/Scanner/__init__.py b/src/engine/SCons/Scanner/__init__.py index 53c67e4..b2e04a3 100644 --- a/src/engine/SCons/Scanner/__init__.py +++ b/src/engine/SCons/Scanner/__init__.py @@ -97,7 +97,7 @@ class Base: self.skeys = skeys self.node_factory = node_factory - def scan(self, node, env): + def scan(self, node, env, target): """ This method scans a single object. 'node' is the node that will be passed to the scanner function, and 'env' is the @@ -106,9 +106,9 @@ class Base: """ if not self.argument is _null: - list = self.function(node, env, self.argument) + list = self.function(node, env, target, self.argument) else: - list = self.function(node, env) + list = self.function(node, env, target) kw = {} if hasattr(node, 'dir'): kw['directory'] = node.dir @@ -119,16 +119,6 @@ class Base: nodes.append(l) return nodes - def instance(self, env): - """ - Return an instance of a Scanner object for use in scanning. - - In the base class, we just return the scanner itself. - Other Scanner classes may use this to clone copies and/or - return unique instances as needed. - """ - return self - def __cmp__(self, other): return cmp(self.__dict__, other.__dict__) @@ -143,7 +133,7 @@ class Recursive(Base): list of all dependencies. """ - def scan(self, node, env): + def scan(self, node, env, target): """ This method does the actual scanning. 'node' is the node that will be passed to the scanner function, and 'env' is the @@ -158,7 +148,7 @@ class Recursive(Base): while nodes: n = nodes.pop(0) d = filter(lambda x, seen=seen: not seen.has_key(x), - Base.scan(self, n, env)) + Base.scan(self, n, env, target)) if d: deps.extend(d) nodes.extend(d) diff --git a/src/engine/SCons/Sig/SigTests.py b/src/engine/SCons/Sig/SigTests.py index 71d48ce..90488e1 100644 --- a/src/engine/SCons/Sig/SigTests.py +++ b/src/engine/SCons/Sig/SigTests.py @@ -50,7 +50,7 @@ class DummyNode: self.file = file self.path = file.path self.builder = file.builder - self.depends = [] + self.depends = [] self.ignore = [] self.use_signature = 1 self.bsig = None @@ -81,11 +81,11 @@ class DummyNode: self.exists_cache = self.exists() return self.exists_cache - def children(self, scanner): + def children(self): return filter(lambda x, i=self.ignore: x not in i, self.sources + self.depends) - def all_children(self, scanner): + def all_children(self): return self.sources + self.depends def current(self): @@ -139,12 +139,12 @@ def create_nodes(files): nodes[0].sources = [] nodes[1].sources = [nodes[0]] - nodes[2].sources = [nodes[3]] + nodes[2].sources = [] nodes[3].sources = [] - nodes[4].sources = [nodes[5]] + nodes[4].sources = [] nodes[5].sources = [nodes[6]] nodes[6].sources = [nodes[5]] - nodes[7].sources = [nodes[2], nodes[4]] + nodes[7].sources = [nodes[2], nodes[4], nodes[3], nodes[5]] nodes[8].sources = [] nodes[9].sources = [nodes[8]] nodes[10].sources = [nodes[9]] @@ -296,9 +296,9 @@ class CalcTestCase(unittest.TestCase): self.ignore = [] self.builder = None self.use_signature = 1 - def children(self, scanner): + def children(self): return filter(lambda x, i=self.ignore: x not in i, self.kids) - def all_children(self, scanner): + def all_children(self): return self.kids def exists(self): return 1 diff --git a/src/engine/SCons/Sig/__init__.py b/src/engine/SCons/Sig/__init__.py index 3770adf..cb03630 100644 --- a/src/engine/SCons/Sig/__init__.py +++ b/src/engine/SCons/Sig/__init__.py @@ -192,36 +192,14 @@ class Calculator: if not bsig is None: return bsig - # Collect the signatures for ALL the nodes that this - # node depends on. Just collecting the direct - # dependants is not good enough, because - # the signature of a non-derived file does - # not include the signatures of its psuedo-sources - # (e.g. the signature for a .c file does not include - # the signatures of the .h files that it includes). - - # However, we do NOT want to walk dependencies of non- - # derived files, because calling get_signature() on the - # derived nodes will in turn call bsig() again and do that - # for us. Hence: sigs = [] - def non_derived(n, parent, myself=node): - if not n.builder or n is myself: - return filter(lambda x, i=myself.ignore: x not in i, - n.all_children(None)) - return [] - def get_sig(n, parent, self=self, myself=node, sigs=sigs): - if not n is myself: - sigs.append(self.get_signature(n)) - walker = SCons.Node.Walker(node, non_derived, eval_func=get_sig) - child = walker.next() - while child: - child = walker.next() - + for child in node.children(): + sigs.append(self.get_signature(child)) if node.builder: sigs.append(self.module.signature(node.builder_sig_adapter())) - return self.module.collect(filter(lambda x: not x is None, sigs)) + return self.module.collect(filter(lambda x: not x is None, sigs)) + def csig(self, node): """ Generate a node's content signature, the digested signature @@ -239,7 +217,7 @@ class Calculator: return csig return self.module.signature(node) - + def get_signature(self, node): """ Get the appropriate signature for a node. diff --git a/src/engine/SCons/Taskmaster.py b/src/engine/SCons/Taskmaster.py index c97d28c..47707b0 100644 --- a/src/engine/SCons/Taskmaster.py +++ b/src/engine/SCons/Taskmaster.py @@ -54,13 +54,16 @@ class Task: Note that it's generally a good idea for sub-classes to call these methods explicitly to update state, etc., rather than roll their own interaction with Taskmaster from scratch.""" - def __init__(self, tm, targets, top, scanner = None): + def __init__(self, tm, targets, top, node): self.tm = tm self.targets = targets self.top = top - self.scanner = scanner + self.node = node def execute(self): + # This methods is called from multiple threads in + # a parallel build, so only do thread safe stuff here. + # Do thread unsafe stuff in executed() or failed(). if self.targets[0].get_state() != SCons.Node.up_to_date: self.targets[0].prepare() self.targets[0].build() @@ -68,7 +71,7 @@ class Task: def get_target(self): """Fetch the target being built or updated by this task. """ - return self.targets[0] + return self.node def set_tstates(self, state): """Set all of the target nodes's states.""" @@ -83,21 +86,14 @@ class Task: things. Most importantly, this calls back to the Taskmaster to put any node tasks waiting on this one back on the pending list.""" + if self.targets[0].get_state() == SCons.Node.executing: self.set_tstates(SCons.Node.executed) for t in self.targets: t.store_sigs() - parents = {} - for p in reduce(lambda x, y: x + y.get_parents(), self.targets, []): - parents[p] = 1 - ready = filter(lambda x, s=self.scanner: - (x.get_state() == SCons.Node.pending - and x.children_are_executed(s)), - parents.keys()) - tasks = {} - for t in map(lambda r: r.task, ready): - tasks[t] = 1 - self.tm.pending_to_ready(tasks.keys()) + t.built() + + self.tm.executed(self.node) def failed(self): """Default action when a task fails: stop the build.""" @@ -114,20 +110,15 @@ class Task: This sets failure status on the target nodes and all of their dependent parent nodes. """ - nodes = {} for t in self.targets: def get_parents(node, parent): return node.get_parents() - def set_nodes(node, parent, nodes=nodes): nodes[node] = 1 - walker = SCons.Node.Walker(t, get_parents, eval_func=set_nodes) + def set_state(node, parent): node.set_state(SCons.Node.failed) + walker = SCons.Node.Walker(t, get_parents, eval_func=set_state) n = walker.next() while n: n = walker.next() - pending = filter(lambda x: x.get_state() == SCons.Node.pending, - nodes.keys()) - tasks = {} - for t in map(lambda r: r.task, pending): - tasks[t] = 1 - self.tm.pending_remove(tasks.keys()) + + self.tm.executed(self.node) def make_ready(self): """Make a task ready for execution.""" @@ -138,9 +129,6 @@ class Task: if not self.tm.calc.current(t, bsig): state = SCons.Node.executing self.set_tstates(state) - self.tm.add_ready(self) - - class Calc: def bsig(self, node): @@ -156,8 +144,6 @@ class Calc: """ return 0 - - class Taskmaster: """A generic Taskmaster for handling a bunch of targets. @@ -166,118 +152,113 @@ class Taskmaster: """ def __init__(self, targets=[], tasker=Task, calc=Calc()): + self.targets = targets # top level targets + self.candidates = targets[:] # nodes that might be ready to be executed + self.candidates.reverse() + self.executing = [] # nodes that are currently executing + self.pending = [] # nodes that depend on a currently executing node + self.tasker = tasker + self.ready = None # the next task that is ready to be executed + self.calc = calc + + def _find_next_ready_node(self): + """Find the next node that is ready to be built""" + + if self.ready: + return - def out_of_date(node, parent): - if node.get_state(): - # The state is set, so someone has already been here - # (finished or currently executing). Find another one. - return [] - # Scan the file before fetching its children(). - if parent: - scanner = parent.src_scanner_get(node.scanner_key()) - else: - scanner = None - return filter(lambda x: x.get_state() != SCons.Node.up_to_date, - node.children(scanner)) + while self.candidates: + node = self.candidates[-1] + state = node.get_state() + + # Skip nodes that have already been executed: + if state != None and state != SCons.Node.stack: + self.candidates.pop() + continue + + # keep track of which nodes are in the execution stack: + node.set_state(SCons.Node.stack) - def cycle_error(node, stack): - if node.builder: - nodes = stack + [node] + children = node.children() + + # detect dependency cycles: + def in_stack(node): return node.get_state() == SCons.Node.stack + cycle = filter(in_stack, children) + if cycle: + nodes = filter(in_stack, self.candidates) + cycle nodes.reverse() desc = "Dependency cycle: " + string.join(map(str, nodes), " -> ") raise SCons.Errors.UserError, desc - def eval_node(node, parent, self=self): - if node.get_state(): - # The state is set, so someone has already been here - # (finished or currently executing). Find another one. - return - if not node.builder: - # It's a source file, we don't need to build it, - # but mark it as "up to date" so targets won't - # wait for it. - node.set_state(SCons.Node.up_to_date) - # set the signature for non-derived files - # here so they don't get recalculated over - # and over again: - node.set_csig(self.calc.csig(node)) - return - try: - tlist = node.builder.targets(node) - except AttributeError: - tlist = [ node ] - if parent: - scanner = parent.src_scanner_get(node.scanner_key()) + for child in children: + if not child.builder: + # set the signature for non-derived files + # here so they don't get recalculated over + # and over again: + child.set_csig(self.calc.csig(child)) + + # Add non-derived files that have not been built + # to the candidates list: + def derived(node): + return node.builder and node.get_state() == None + derived = filter(derived, children) + if derived: + derived.reverse() + self.candidates.extend(derived) + continue + + # Skip nodes that are pending on a currently executing node: + if node.depends_on(self.executing) or node.depends_on(self.pending): + self.pending.append(node) + node.set_state(SCons.Node.pending) + self.candidates.pop() + continue else: - scanner = None - task = self.tasker(self, tlist, self.walkers[0].is_done(), scanner) - if not tlist[0].children_are_executed(scanner): - for t in tlist: - t.set_state(SCons.Node.pending) - t.task = task - self.pending = self.pending + 1 - return - task.make_ready() - - #XXX In Python 2.2 we can get rid of f1, f2 and f3: - self.walkers = map(lambda x, f1=out_of_date, - f2=cycle_error, - f3=eval_node: - SCons.Node.Walker(x, f1, f2, f3), - targets) - self.tasker = tasker - self.calc = calc - self.ready = [] - self.pending = 0 - - self._find_next_ready_node() + self.candidates.pop() + self.ready = node + break def next_task(self): """Return the next task to be executed.""" - if self.ready: - task = self.ready.pop() - if not self.ready: - self._find_next_ready_node() - return task - else: - return None + + self._find_next_ready_node() - def _find_next_ready_node(self): - """Find the next node that is ready to be built""" - while self.walkers: - n = self.walkers[0].next() - if n == None: - self.walkers.pop(0) - continue - if self.ready: - return + node = self.ready + + if node is None: + return None + + self.executing.append(node) + try: + tlist = node.builder.targets(node) + except AttributeError: + tlist = [node] + task = self.tasker(self, tlist, node in self.targets, node) + task.make_ready() + self.ready = None + + return task def is_blocked(self): + self._find_next_ready_node() + return not self.ready and self.pending def stop(self): """Stop the current build completely.""" - self.walkers = [] - self.pending = 0 - self.ready = [] + self.candidates = [] + self.ready = None + self.pending = [] - def add_ready(self, task): - """Add a task to the ready queue. - """ - self.ready.append(task) - - def pending_to_ready(self, tasks): - """Move the specified tasks from the pending count - to the 'ready' queue. - """ - self.pending_remove(tasks) - for t in tasks: - t.make_ready() - - def pending_remove(self, tasks): - """Remove tasks from the pending count. + def executed(self, node): + self.executing.remove(node) - We assume that the caller has already confirmed that - the nodes in this task are in pending state. - """ - self.pending = self.pending - len(tasks) + # move the current pending nodes to the candidates list: + # (they may not all be ready to build, but _find_next_ready_node() + # will figure out which ones are really ready) + for node in self.pending: + node.set_state(None) + self.pending.reverse() + self.candidates.extend(self.pending) + self.pending = [] + diff --git a/src/engine/SCons/TaskmasterTests.py b/src/engine/SCons/TaskmasterTests.py index b6fa685..44b1e40 100644 --- a/src/engine/SCons/TaskmasterTests.py +++ b/src/engine/SCons/TaskmasterTests.py @@ -30,7 +30,7 @@ import SCons.Taskmaster import SCons.Errors -built = None +built_text = None executed = None scan_called = 0 @@ -39,8 +39,8 @@ class Node: self.name = name self.kids = kids self.scans = scans - self.scanned = {} - self.src_scanners = {} + self.scanned = 0 + self.scanner = None self.builder = Node.build self.bsig = None self.csig = None @@ -51,32 +51,30 @@ class Node: kid.parents.append(self) def build(self): - global built - built = self.name + " built" + global built_text + built_text = self.name + " built" + + def built(self): + global built_text + built_text = built_text + " really" def prepare(self): pass - def children(self, scanner): - if not self.scanned.get(scanner, None): - self.scan(scanner) - self.scanned[scanner] = 1 + def children(self): + if not self.scanned: + self.scan() + self.scanned = 1 return self.kids - def scan(self, scanner): - global scan_called - scan_called = scan_called + 1 + def scan(self): + global scan_called + scan_called = scan_called + 1 self.kids = self.kids + self.scans for scan in self.scans: scan.parents.append(self) self.scans = [] - def src_scanner_set(self, key, scanner): - self.src_scanners[key] = scanner - - def src_scanner_get(self, key): - return self.src_scanners.get(key, None) - def scanner_key(self): return self.name @@ -98,12 +96,13 @@ class Node: def store_sigs(self): pass - def children_are_executed(self, scanner): - return reduce(lambda x,y: ((y.get_state() == SCons.Node.executed - or y.get_state() == SCons.Node.up_to_date) - and x), - self.children(scanner), - 1) + + def depends_on(self, nodes): + for node in nodes: + if node in self.kids: + return 1 + return 0 + def __str__(self): return self.name @@ -111,14 +110,14 @@ class Node: class TaskmasterTestCase(unittest.TestCase): def test_next_task(self): - """Test fetching the next task - """ - global built + """Test fetching the next task + """ + global built_text - n1 = Node("n1") + n1 = Node("n1") tm = SCons.Taskmaster.Taskmaster([n1, n1]) t = tm.next_task() - t.executed() + t.execute() t = tm.next_task() assert t == None @@ -126,26 +125,26 @@ class TaskmasterTestCase(unittest.TestCase): n2 = Node("n2") n3 = Node("n3", [n1, n2]) - tm = SCons.Taskmaster.Taskmaster([n3]) + tm = SCons.Taskmaster.Taskmaster([n3]) t = tm.next_task() t.execute() - assert built == "n1 built" + assert built_text == "n1 built", built_text t.executed() t = tm.next_task() t.execute() - assert built == "n2 built" + assert built_text == "n2 built", built_text t.executed() t = tm.next_task() t.execute() - assert built == "n3 built" + assert built_text == "n3 built", built_text t.executed() assert tm.next_task() == None - built = "up to date: " + built_text = "up to date: " top_node = n3 class MyCalc(SCons.Taskmaster.Calc): @@ -154,12 +153,12 @@ class TaskmasterTestCase(unittest.TestCase): class MyTask(SCons.Taskmaster.Task): def execute(self): - global built + global built_text if self.targets[0].get_state() == SCons.Node.up_to_date: if self.top: - built = self.targets[0].name + " up-to-date top" + built_text = self.targets[0].name + " up-to-date top" else: - built = self.targets[0].name + " up-to-date" + built_text = self.targets[0].name + " up-to-date" else: self.targets[0].build() @@ -171,24 +170,24 @@ class TaskmasterTestCase(unittest.TestCase): t = tm.next_task() t.execute() - assert built == "n1 up-to-date" + assert built_text == "n1 up-to-date", built_text t.executed() t = tm.next_task() t.execute() - assert built == "n2 up-to-date" + assert built_text == "n2 up-to-date", built_text t.executed() t = tm.next_task() t.execute() - assert built == "n3 up-to-date top" + assert built_text == "n3 up-to-date top", built_text t.executed() - assert tm.next_task() == None + assert tm.next_task() == None n1 = Node("n1") - n2 = Node("n2") + n2 = Node("n2") n3 = Node("n3", [n1, n2]) n4 = Node("n4") n5 = Node("n5", [n3, n4]) @@ -221,7 +220,7 @@ class TaskmasterTestCase(unittest.TestCase): t3.executed() assert not tm.is_blocked() t5 = tm.next_task() - assert t5.get_target() == n5 + assert t5.get_target() == n5, t5.get_target() assert not tm.is_blocked() assert tm.next_task() == None @@ -261,35 +260,35 @@ class TaskmasterTestCase(unittest.TestCase): t.executed() assert tm.next_task() == None - n1 = Node("n1") - n2 = Node("n2") - n3 = Node("n3", [n1, n2]) - n4 = Node("n4", [n3]) - n5 = Node("n5", [n3]) - global scan_called - scan_called = 0 - tm = SCons.Taskmaster.Taskmaster([n4]) - t = tm.next_task() + n1 = Node("n1") + n2 = Node("n2") + n3 = Node("n3", [n1, n2]) + n4 = Node("n4", [n3]) + n5 = Node("n5", [n3]) + global scan_called + scan_called = 0 + tm = SCons.Taskmaster.Taskmaster([n4]) + t = tm.next_task() assert t.get_target() == n1 - t.executed() - t = tm.next_task() + t.executed() + t = tm.next_task() assert t.get_target() == n2 - t.executed() - t = tm.next_task() + t.executed() + t = tm.next_task() assert t.get_target() == n3 - t.executed() - t = tm.next_task() + t.executed() + t = tm.next_task() assert t.get_target() == n4 - t.executed() - assert tm.next_task() == None - assert scan_called == 4, scan_called + t.executed() + assert tm.next_task() == None + assert scan_called == 4, scan_called - tm = SCons.Taskmaster.Taskmaster([n5]) - t = tm.next_task() + tm = SCons.Taskmaster.Taskmaster([n5]) + t = tm.next_task() assert t.get_target() == n5, t.get_target() - t.executed() - assert tm.next_task() == None - assert scan_called == 5, scan_called + t.executed() + assert tm.next_task() == None + assert scan_called == 5, scan_called def test_cycle_detection(self): n1 = Node("n1") @@ -302,30 +301,30 @@ class TaskmasterTestCase(unittest.TestCase): tm = SCons.Taskmaster.Taskmaster([n3]) t = tm.next_task() except SCons.Errors.UserError, e: - assert str(e) == "Dependency cycle: n3 -> n1 -> n2 -> n3" + assert str(e) == "Dependency cycle: n3 -> n1 -> n2 -> n3", str(e) else: assert 0 def test_is_blocked(self): """Test whether a task is blocked - Both default and overridden in a subclass. - """ - tm = SCons.Taskmaster.Taskmaster() - assert not tm.is_blocked() + Both default and overridden in a subclass. + """ + tm = SCons.Taskmaster.Taskmaster() + assert not tm.is_blocked() - class MyTM(SCons.Taskmaster.Taskmaster): - def is_blocked(self): - return 1 - tm = MyTM() - assert tm.is_blocked() == 1 + class MyTM(SCons.Taskmaster.Taskmaster): + def is_blocked(self): + return 1 + tm = MyTM() + assert tm.is_blocked() == 1 def test_stop(self): """Test the stop() method Both default and overridden in a subclass. """ - global built + global built_text n1 = Node("n1") n2 = Node("n2") @@ -334,48 +333,33 @@ class TaskmasterTestCase(unittest.TestCase): tm = SCons.Taskmaster.Taskmaster([n3]) t = tm.next_task() t.execute() - assert built == "n1 built" + assert built_text == "n1 built", built_text t.executed() + assert built_text == "n1 built really", built_text tm.stop() assert tm.next_task() is None class MyTM(SCons.Taskmaster.Taskmaster): def stop(self): - global built - built = "MyTM.stop()" + global built_text + built_text = "MyTM.stop()" SCons.Taskmaster.Taskmaster.stop(self) n1 = Node("n1") n2 = Node("n2") n3 = Node("n3", [n1, n2]) - built = None + built_text = None tm = MyTM([n3]) tm.next_task().execute() - assert built == "n1 built" + assert built_text == "n1 built" tm.stop() - assert built == "MyTM.stop()" + assert built_text == "MyTM.stop()" assert tm.next_task() is None - def test_add_ready(self): - """Test adding a task to the ready queue""" - class MyTask: - def __init__(self, tm, tlist, top, scanner): - pass - def make_ready(self): - pass - n1 = Node("n1") - tm = SCons.Taskmaster.Taskmaster([n1], tasker = MyTask) - task = MyTask(tm, [], 0, None) - tm.add_ready(task) - assert tm.ready == [ task ], tm.ready - - def test_pending_to_ready(self): - pass - - def test_pending_remove(self): + def test_executed(self): pass @@ -383,4 +367,4 @@ class TaskmasterTestCase(unittest.TestCase): if __name__ == "__main__": suite = unittest.makeSuite(TaskmasterTestCase, 'test_') if not unittest.TextTestRunner().run(suite).wasSuccessful(): - sys.exit(1) + sys.exit(1) |