diff options
author | Steven Knight <knight@baldmt.com> | 2004-05-19 17:49:55 (GMT) |
---|---|---|
committer | Steven Knight <knight@baldmt.com> | 2004-05-19 17:49:55 (GMT) |
commit | 04ebe107191659f805bd63148c61c1026efeb686 (patch) | |
tree | 9122729d1120c1119fe10ff360f4675789ca8f9b | |
parent | 12d2ae1193b9e32e0164218bb73240791914f976 (diff) | |
download | SCons-04ebe107191659f805bd63148c61c1026efeb686.zip SCons-04ebe107191659f805bd63148c61c1026efeb686.tar.gz SCons-04ebe107191659f805bd63148c61c1026efeb686.tar.bz2 |
Fix spurious rebuilds/reinstalls of header files and circular dependencies with generated header files by allowing Scanners to be associated explicitly with Builders, not just through Scanner file suffix lists.
30 files changed, 795 insertions, 231 deletions
diff --git a/doc/man/scons.1 b/doc/man/scons.1 index 88e5b47..ea0cf81 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -2197,9 +2197,9 @@ SConscript file.) '\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .TP -.RI Builder( action ", [" multi ", " prefix ", " suffix ", " src_suffix ", " src_builder ", " emitter ]) +.RI Builder( action ", [" arguments ]) .TP -.RI env.Builder( action ", [" multi ", " prefix ", " suffix ", " src_suffix ", " src_builder ", " emitter ]) +.RI env.Builder( action ", [" arguments ]) Creates a Builder object for the specified .IR action . @@ -5173,6 +5173,14 @@ path, you must make it absolute yourself. .IP SCANNERS A list of the available implicit dependency scanners. +New file scanners may be added by +appending to this list, +although the more flexible approach +is to associate scanners +with a specific Builder. +See the sections "Builder Objects" +and "Scanner Objects," +below, for more information. .IP SCCS The SCCS executable. @@ -6257,15 +6265,6 @@ takes three arguments: .I env - the construction environment. -.IP multi -Specifies whether this builder is allowed to be called multiple times for -the same target file(s). The default is 0, which means the builder -can not be called multiple times for the same target file(s). Calling a -builder multiple times for the same target simply adds additional source -files to the target; it is not allowed to change the environment associated -with the target, specify addition environment overrides, or associate a different -builder with the target. - .IP prefix The prefix that will be prepended to the target file name. This may be a simple string, or a callable object that takes @@ -6308,10 +6307,33 @@ b = Builder("build_it < $SOURCE > $TARGET" .IP src_suffix The expected source file name suffix. -.IP src_builder -Specifies a builder to use when a source file name suffix does not match -any of the suffixes of the builder. Using this argument produces a -multi-stage builder. +.IP target_scanner +A Scanner object that +will be invoked to find +implicit dependencies for this target file. +This keyword argument should be used +for Scanner objects that find +implicit dependencies +based only on the target file +and the construction environment, +.I not +for implicit +(See the section "Scanner Objects," below, +for information about creating Scanner objects.) + +.IP source_scanner +A Scanner object that +will be invoked to +find implicit dependences in +any source files +used to build this target file. +This is where you would +specify a scanner to +find things like +.B #include +lines in source files. +(See the section "Scanner Objects," below, +for information about creating Scanner objects.) .IP emitter A function or list of functions to manipulate the target and source @@ -6376,6 +6398,24 @@ b = Builder("my_build < $TARGET > $SOURCE", '.suf2' : e_suf2}) .EE +.IP multi +Specifies whether this builder is allowed to be called multiple times for +the same target file(s). The default is 0, which means the builder +can not be called multiple times for the same target file(s). Calling a +builder multiple times for the same target simply adds additional source +files to the target; it is not allowed to change the environment associated +with the target, specify addition environment overrides, or associate a different +builder with the target. + +.IP env +A construction environment that can be used +to fetch source code using this Builder. +(Note that this environment is +.I not +used for normal builds of normal target files, +which use the environment that was +used to call the Builder for the target file.) + .IP generator A function that returns a list of actions that will be executed to build the target(s) from the source(s). @@ -6406,21 +6446,18 @@ def g(source, target, env, for_signature): b = Builder(generator=g) .EE +.IP src_builder +Specifies a builder to use when a source file name suffix does not match +any of the suffixes of the builder. Using this argument produces a +multi-stage builder. + +.RE The .I generator and .I action arguments must not both be used for the same Builder. -.IP env -A construction environment that can be used -to fetch source code using this Builder. -(Note that this environment is -.I not -used for normal builds of normal target files, -which use the environment that was -used to call the Builder for the target file.) - Any additional keyword arguments supplied when a Builder object is created (that is, when the Builder() function is called) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index b1d3119..7aaabf5 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -119,6 +119,14 @@ RELEASE 0.96 - XXX the parents's implicit dependencies, let returning up the normal Taskmaster descent take care of it for us. + - Add documented support for separate target_scanner and source_scanner + arguments to Builder creation, which allows different scanners to + be applied to source files + + - Don't re-install or (re-generate) .h files when a subsidiary #included + .h file changes. This eliminates incorrect circular dependencies + with .h files generated from other source files. + From Gary Oberbrunner: - Add a --debug=presub option to print actions prior to substitution. diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index 5c75a0f..4aa518f 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -300,7 +300,7 @@ def _init_nodes(builder, env, overrides, tlist, slist): elif t.overrides != overrides: raise UserError, "Two different sets of overrides were specified for the same target: %s"%str(t) - elif builder.scanner and t.target_scanner and builder.scanner != t.target_scanner: + elif builder.target_scanner and t.target_scanner and builder.target_scanner != t.target_scanner: raise UserError, "Two different scanners were specified for the same target: %s"%str(t) if builder.multi: @@ -338,15 +338,22 @@ def _init_nodes(builder, env, overrides, tlist, slist): t.env_set(env) t.add_source(slist) t.set_executor(executor) - if builder.scanner: - t.target_scanner = builder.scanner - if not t.source_scanner: - t.source_scanner = env.get_scanner(t.scanner_key()) - - # Last, add scanners from the Environment to the source Nodes. + if builder.target_scanner: + t.target_scanner = builder.target_scanner + if t.source_scanner is None: + t.source_scanner = builder.source_scanner + + # Add backup source scanners from the environment to the source + # nodes. This may not be necessary if the node will have a real + # source scanner added later (which is why these are the "backup" + # source scanners, not the real ones), but because source nodes may + # be used multiple times for different targets, it ends up being + # more efficient to do this calculation once here, as opposed to + # delaying it until later when we potentially have to calculate it + # over and over and over. for s in slist: - if not s.source_scanner: - s.source_scanner = env.get_scanner(s.scanner_key()) + if s.source_scanner is None and s.backup_source_scanner is None: + s.backup_source_scanner = env.get_scanner(s.scanner_key()) class EmitterProxy: """This is a callable class that can act as a @@ -389,7 +396,8 @@ class BuilderBase: src_suffix = '', target_factory = SCons.Node.FS.default_fs.File, source_factory = SCons.Node.FS.default_fs.File, - scanner = None, + target_scanner = None, + source_scanner = None, emitter = None, multi = 0, env = None, @@ -416,7 +424,8 @@ class BuilderBase: self.target_factory = target_factory self.source_factory = source_factory - self.scanner = scanner + self.target_scanner = target_scanner + self.source_scanner = source_scanner self.emitter = emitter @@ -592,7 +601,8 @@ class ListBuilder(SCons.Util.Proxy): if __debug__: logInstanceCreation(self) SCons.Util.Proxy.__init__(self, builder) self.builder = builder - self.scanner = builder.scanner + self.target_scanner = builder.target_scanner + self.source_scanner = builder.source_scanner self.env = env self.tlist = tlist self.multi = builder.multi @@ -628,12 +638,13 @@ class MultiStepBuilder(BuilderBase): src_suffix = '', target_factory = SCons.Node.FS.default_fs.File, source_factory = SCons.Node.FS.default_fs.File, - scanner=None, + target_scanner = None, + source_scanner = None, emitter=None): if __debug__: logInstanceCreation(self) BuilderBase.__init__(self, action, prefix, suffix, src_suffix, target_factory, source_factory, - scanner, emitter) + target_scanner, source_scanner, emitter) if not SCons.Util.is_List(src_builder): src_builder = [ src_builder ] self.src_builder = src_builder diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py index e33d95c..e592250 100644 --- a/src/engine/SCons/BuilderTests.py +++ b/src/engine/SCons/BuilderTests.py @@ -133,6 +133,7 @@ class MyNode_without_target_from_source: self.builder = None self.side_effect = 0 self.source_scanner = None + self.backup_source_scanner = None def __str__(self): return self.name def builder_set(self, builder): @@ -753,25 +754,30 @@ class BuilderTestCase(unittest.TestCase): match = str(e) == "While building `['t8']': Don't know how to build a file with suffix `.unknown'." assert match, e - def test_build_scanner(self): - """Testing ability to set a target scanner through a builder.""" + def test_target_scanner(self): + """Testing ability to set target and source scanners through a builder.""" global instanced class TestScanner: pass - scn = TestScanner() + tscan = TestScanner() + sscan = TestScanner() env = Environment() - builder = SCons.Builder.Builder(scanner=scn) + builder = SCons.Builder.Builder(target_scanner=tscan, + source_scanner=sscan) tgt = builder(env, target='foo2', source='bar') - assert tgt.target_scanner == scn, tgt.target_scanner + assert tgt.target_scanner == tscan, tgt.target_scanner + assert tgt.source_scanner == sscan, tgt.source_scanner builder1 = SCons.Builder.Builder(action='foo', src_suffix='.bar', suffix='.foo') builder2 = SCons.Builder.Builder(action='foo', src_builder = builder1, - scanner = scn) + target_scanner = tscan, + source_scanner = tscan) tgt = builder2(env, target='baz2', source='test.bar test2.foo test3.txt') - assert tgt.target_scanner == scn, tgt.target_scanner + assert tgt.target_scanner == tscan, tgt.target_scanner + assert tgt.source_scanner == tscan, tgt.source_scanner def test_src_scanner(slf): """Testing ability to set a source file scanner through a builder.""" @@ -784,12 +790,14 @@ class BuilderTestCase(unittest.TestCase): scanner = TestScanner() builder = SCons.Builder.Builder(action='action') - # With no scanner specified, source_scanner is None. + # With no scanner specified, source_scanner and + # backup_source_scanner are None. env1 = Environment() tgt = builder(env1, target='foo1.x', source='bar.y') src = tgt.sources[0] assert tgt.target_scanner != scanner, tgt.target_scanner assert src.source_scanner is None, src.source_scanner + assert src.backup_source_scanner is None, src.backup_source_scanner # Later use of the same source file with an environment that # has a scanner must still set the scanner. @@ -798,7 +806,8 @@ class BuilderTestCase(unittest.TestCase): tgt = builder(env2, target='foo2.x', source='bar.y') src = tgt.sources[0] assert tgt.target_scanner != scanner, tgt.target_scanner - assert src.source_scanner == scanner, src.source_scanner + assert src.source_scanner is None, src.source_scanner + assert src.backup_source_scanner == scanner, src.backup_source_scanner def test_Builder_Args(self): """Testing passing extra args to a builder.""" diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py index 7a47b21..4c42f28 100644 --- a/src/engine/SCons/Defaults.py +++ b/src/engine/SCons/Defaults.py @@ -96,6 +96,8 @@ def SharedFlagChecker(source, target, env): SharedCheck = SCons.Action.Action(SharedFlagChecker, None) # Scanners and suffixes for common languages. +ObjSourceScan = SCons.Scanner.Scanner({}) + CScan = SCons.Scanner.C.CScan() CSuffixes = [".c", ".C", ".cxx", ".cpp", ".c++", ".cc", @@ -103,14 +105,23 @@ CSuffixes = [".c", ".C", ".cxx", ".cpp", ".c++", ".cc", ".F", ".fpp", ".FPP", ".S", ".spp", ".SPP"] +for suffix in CSuffixes: + ObjSourceScan.add_scanner(suffix, CScan) + DScan = SCons.Scanner.D.DScan() DSuffixes = ['.d'] +for suffix in DSuffixes: + ObjSourceScan.add_scanner(suffix, DScan) + FortranScan = SCons.Scanner.Fortran.FortranScan() FortranSuffixes = [".f", ".F", ".for", ".FOR"] +for suffix in FortranSuffixes: + ObjSourceScan.add_scanner(suffix, FortranScan) + IDLSuffixes = [".idl", ".IDL"] # Actions for common languages. @@ -330,7 +341,7 @@ class NullCmdGenerator: ConstructionEnvironment = { 'BUILDERS' : {}, - 'SCANNERS' : [CScan, FortranScan, DScan], + 'SCANNERS' : [], 'CPPSUFFIXES': CSuffixes, 'DSUFFIXES' : DSuffixes, 'FORTRANSUFFIXES': FortranSuffixes, diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index b920643..d43a99f 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -395,6 +395,7 @@ class Base: try: scanners = self._dict['SCANNERS'] except KeyError: + self.scanner_map = {} return None else: self.scanner_map = sm = {} @@ -402,6 +403,8 @@ class Base: # claim they can scan the same suffix, earlier scanners # in the list will overwrite later scanners, so that # the result looks like a "first match" to the user. + if not SCons.Util.is_List(scanners): + scanners = [scanners] scanners.reverse() for scanner in scanners: for k in scanner.get_skeys(self): @@ -1159,7 +1162,7 @@ class Base: arg = self.subst(arg) nargs.append(arg) nkw = self.subst_kw(kw) - return apply(SCons.Scanner.Base, nargs, nkw) + return apply(SCons.Scanner.Scanner, nargs, nkw) def SConsignFile(self, name=".sconsign", dbm_module=None): name = self.subst(name) diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index 15434cb..0d158a1 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -488,12 +488,6 @@ class Base(SCons.Node.Node): self._rexists = self.rfile().exists() return self._rexists - def get_parents(self): - parents = SCons.Node.Node.get_parents(self) - if self.dir and not isinstance(self.dir, ParentOfRoot): - parents.append(self.dir) - return parents - def is_under(self, dir): if self is dir: return 1 @@ -1117,8 +1111,7 @@ class Dir(Base): self.abspath_ = self.abspath + os.sep self.repositories = [] self.srcdir = None - self.source_scanner = None - + self.entries = {} self.entries['.'] = self self.entries['..'] = self.dir diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py index fb2e0fa..f69dd4e 100644 --- a/src/engine/SCons/Node/FSTests.py +++ b/src/engine/SCons/Node/FSTests.py @@ -60,6 +60,8 @@ class Scanner: return [self.node] def __hash__(self): return self.hash + def select(self, node): + return self class Environment: def __init__(self): @@ -1034,12 +1036,6 @@ class FSTestCase(unittest.TestCase): skey = fs.Dir('ddd.x').scanner_key() assert skey is None, skey - d1 = fs.Dir('dir') - f1 = fs.File('dir/file') - assert f1.dir == d1, f1.dir - parents = f1.get_parents() - assert parents == [ d1 ], parents - test.write("i_am_not_a_directory", "\n") try: exc_caught = 0 diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py index 535c84e..e36d6ce 100644 --- a/src/engine/SCons/Node/NodeTests.py +++ b/src/engine/SCons/Node/NodeTests.py @@ -138,6 +138,8 @@ class Scanner: def __call__(self, node): self.called = 1 return node.found_includes + def select(self, node): + return self class MyNode(SCons.Node.Node): """The base Node class contains a number of do-nothing methods that @@ -543,12 +545,6 @@ class NodeTestCase(unittest.TestCase): node.add_dependency([three, four, one]) assert node.depends == [zero, one, two, three, four] - assert zero.get_parents() == [node] - assert one.get_parents() == [node] - assert two.get_parents() == [node] - assert three.get_parents() == [node] - assert four.get_parents() == [node] - try: node.add_depends([[five, six]]) except: @@ -556,8 +552,6 @@ class NodeTestCase(unittest.TestCase): else: raise "did not catch expected exception" assert node.depends == [zero, one, two, three, four] - assert five.get_parents() == [] - assert six.get_parents() == [] def test_add_source(self): @@ -583,12 +577,6 @@ class NodeTestCase(unittest.TestCase): node.add_source([three, four, one]) assert node.sources == [zero, one, two, three, four] - assert zero.get_parents() == [node] - assert one.get_parents() == [node] - assert two.get_parents() == [node] - assert three.get_parents() == [node] - assert four.get_parents() == [node] - try: node.add_source([[five, six]]) except: @@ -596,8 +584,6 @@ class NodeTestCase(unittest.TestCase): else: raise "did not catch expected exception" assert node.sources == [zero, one, two, three, four] - assert five.get_parents() == [] - assert six.get_parents() == [] def test_add_ignore(self): """Test adding files whose dependencies should be ignored. @@ -622,12 +608,6 @@ class NodeTestCase(unittest.TestCase): node.add_ignore([three, four, one]) assert node.ignore == [zero, one, two, three, four] - assert zero.get_parents() == [node] - assert one.get_parents() == [node] - assert two.get_parents() == [node] - assert three.get_parents() == [node] - assert four.get_parents() == [node] - try: node.add_ignore([[five, six]]) except: @@ -635,8 +615,6 @@ class NodeTestCase(unittest.TestCase): else: raise "did not catch expected exception" assert node.ignore == [zero, one, two, three, four] - assert five.get_parents() == [] - assert six.get_parents() == [] def test_get_found_includes(self): """Test the default get_found_includes() method @@ -689,6 +667,33 @@ class NodeTestCase(unittest.TestCase): deps = node.get_implicit_deps(env, s, target) assert deps == [d, e, f], map(str, deps) + def test_get_source_scanner(self): + """Test fetching the source scanner for a Node + """ + class Builder: + pass + target = SCons.Node.Node() + source = SCons.Node.Node() + s = target.get_source_scanner(source) + assert s is None, s + + ts1 = Scanner() + ts2 = Scanner() + ts3 = Scanner() + + source.backup_source_scanner = ts1 + s = target.get_source_scanner(source) + assert s is ts1, s + + source.builder = Builder() + source.builder.source_scanner = ts2 + s = target.get_source_scanner(source) + assert s is ts2, s + + target.source_scanner = ts3 + s = target.get_source_scanner(source) + assert s is ts3, s + def test_scan(self): """Test Scanner functionality """ @@ -947,6 +952,7 @@ class NodeTestCase(unittest.TestCase): n.includes = 'testincludes' n.found_include = {'testkey':'testvalue'} n.implicit = 'testimplicit' + n.waiting_parents = ['foo', 'bar'] n.clear() @@ -955,6 +961,7 @@ class NodeTestCase(unittest.TestCase): assert n.includes is None, n.includes assert n.found_includes == {}, n.found_includes assert n.implicit is None, n.implicit + assert n.waiting_parents == [], n.waiting_parents def test_get_subst_proxy(self): """Test the get_subst_proxy method.""" @@ -985,6 +992,25 @@ class NodeTestCase(unittest.TestCase): n = SCons.Node.Node() n.postprocess() + def test_add_to_waiting_parents(self): + """Test the add_to_waiting_parents() method""" + n1 = SCons.Node.Node() + n2 = SCons.Node.Node() + assert n1.waiting_parents == [], n1.waiting_parents + n1.add_to_waiting_parents(n2) + assert n1.waiting_parents == [n2], n1.waiting_parents + + def test_call_for_all_waiting_parents(self): + """Test the call_for_all_waiting_parents() method""" + n1 = SCons.Node.Node() + n2 = SCons.Node.Node() + n1.add_to_waiting_parents(n2) + result = [] + def func(node, result=result): + result.append(node) + n1.call_for_all_waiting_parents(func) + assert result == [n1, n2], result + if __name__ == "__main__": diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index 2841759..75b3a6d 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -116,10 +116,11 @@ class Node: self.ignore = [] # dependencies to ignore self.ignore_dict = {} self.implicit = None # implicit (scanned) dependencies (None means not scanned yet) - self.parents = {} + self.waiting_parents = [] self.wkids = None # Kids yet to walk, when it's an array self.target_scanner = None # explicit scanner from this node's Builder - self.source_scanner = None # source scanner + self.source_scanner = None + self.backup_source_scanner = None self.env = None self.state = None @@ -208,20 +209,29 @@ class Node: else: self.store_info(new_binfo) - # 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 - for parent in self.get_parents(): - parent.implicit = None - parent.del_binfo() + # Clear our scanned included files. + self.found_includes = {} + self.includes = None + + # Clear the implicit dependency caches of any Nodes + # waiting for this Node to be built. + for parent in self.waiting_parents: + parent.implicit = None + parent.del_binfo() + self.waiting_parents = [] # The content just changed, delete any cached info # so it will get recalculated. self.del_cinfo() + def add_to_waiting_parents(self, node): + self.waiting_parents.append(node) + + def call_for_all_waiting_parents(self, func): + func(self) + for parent in self.waiting_parents: + parent.call_for_all_waiting_parents(func) + def postprocess(self): """Clean up anything we don't need to hang onto after we've been built.""" @@ -248,6 +258,8 @@ class Node: self.found_includes = {} self.implicit = None + self.waiting_parents = [] + def visited(self): """Called just after this node has been visited without requiring a build..""" @@ -329,6 +341,10 @@ class Node: if not scanner: return [] + # Give the scanner a chance to select a more specific scanner + # for this Node. + scanner = scanner.select(self) + try: recurse = scanner.recursive except AttributeError: @@ -367,6 +383,22 @@ class Node: self.implicit_factory_cache[path] = n return n + def get_source_scanner(self, node): + """Fetch the source scanner for the specified node + + NOTE: "self" is the target being built, "node" is + the source file for which we want to fetch the scanner. + """ + if self.source_scanner: + return self.source_scanner + try: + scanner = node.builder.source_scanner + if scanner: + return scanner + except AttributeError: + pass + return node.backup_source_scanner or None + def scan(self): """Scan this node's dependents for implicit dependencies.""" # Don't bother scanning non-derived files, because we don't @@ -405,20 +437,14 @@ class Node: # self.del_binfo() for child in self.children(scan=0): - scanner = child.source_scanner + scanner = self.get_source_scanner(child) if scanner: - self._add_child(self.implicit, - self.implicit_dict, - child.get_implicit_deps(build_env, - scanner, - self)) + deps = child.get_implicit_deps(build_env, scanner, self) + self._add_child(self.implicit, self.implicit_dict, deps) # scan this node itself for implicit dependencies - self._add_child(self.implicit, - self.implicit_dict, - self.get_implicit_deps(build_env, - self.target_scanner, - self)) + deps = self.get_implicit_deps(build_env, self.target_scanner, self) + self._add_child(self.implicit, self.implicit_dict, deps) # XXX See note above re: --implicit-cache. #if implicit_cache: @@ -632,7 +658,6 @@ class Node: collection.append(c) dict[c] = 1 added = 1 - c.parents[self] = 1 if added: self._children_reset() @@ -686,9 +711,6 @@ class Node: else: return self.sources + self.depends + self.implicit - def get_parents(self): - return self.parents.keys() - def set_state(self, state): self.state = state @@ -738,7 +760,8 @@ class Node: if self.is_derived() and self.env: env = self.get_build_env() for s in self.sources: - def f(node, env=env, scanner=s.source_scanner, target=self): + scanner = s.get_source_scanner(self) + def f(node, env=env, scanner=scanner, target=self): return node.get_found_includes(env, scanner, target) return SCons.Util.render_tree(s, f, 1) else: diff --git a/src/engine/SCons/SConf.py b/src/engine/SCons/SConf.py index db4e375..196b426 100644 --- a/src/engine/SCons/SConf.py +++ b/src/engine/SCons/SConf.py @@ -155,6 +155,17 @@ class SConf: already_done.append( n ) self._setCache(n.children()) + # Calling children() has set up the implicit cache (and + # other state), but we're not really building things yet, + # so generated files won't have been generated. Clear the + # state so we will, in fact, build everything that's necessary + # when we do the build. + # + # XXX - it would be good to find a better way to do this, + # maybe by doing something with the actions in the actual + # Taskmaster...? + n.clear() + def BuildNodes(self, nodes): """ Tries to build the given nodes immediately. Returns 1 on success, diff --git a/src/engine/SCons/SConfTests.py b/src/engine/SCons/SConfTests.py index 8bc11ad..39af3a1 100644 --- a/src/engine/SCons/SConfTests.py +++ b/src/engine/SCons/SConfTests.py @@ -178,6 +178,8 @@ class SConfTestCase(unittest.TestCase): return None def postprocess(self): pass + def clear(self): + pass return [MyNode('n1'), MyNode('n2')] self.scons_env.Append(BUILDERS = {'SConfActionBuilder' : MyBuilder()}) sconf.TryBuild(self.scons_env.SConfActionBuilder) diff --git a/src/engine/SCons/Scanner/CTests.py b/src/engine/SCons/Scanner/CTests.py index f63060a..693ec18 100644 --- a/src/engine/SCons/Scanner/CTests.py +++ b/src/engine/SCons/Scanner/CTests.py @@ -193,6 +193,9 @@ class DummyEnvironment(UserDict.UserDict): path = [path] return map(self.subst, path) + def get_calculator(self): + return None + if os.path.normcase('foo') == os.path.normcase('FOO'): my_normpath = os.path.normcase else: diff --git a/src/engine/SCons/Scanner/FortranTests.py b/src/engine/SCons/Scanner/FortranTests.py index e69f99e..766f0ee 100644 --- a/src/engine/SCons/Scanner/FortranTests.py +++ b/src/engine/SCons/Scanner/FortranTests.py @@ -168,6 +168,9 @@ class DummyEnvironment: path = [path] return map(self.subst, path) + def get_calculator(self): + return None + def deps_match(self, deps, headers): scanned = map(os.path.normpath, map(str, deps)) expect = map(os.path.normpath, headers) diff --git a/src/engine/SCons/Scanner/IDLTests.py b/src/engine/SCons/Scanner/IDLTests.py index c20dedc..876f492 100644 --- a/src/engine/SCons/Scanner/IDLTests.py +++ b/src/engine/SCons/Scanner/IDLTests.py @@ -218,6 +218,9 @@ class DummyEnvironment: def __delitem__(self,key): del self.Dictionary()[key] + def get_calculator(self): + return None + global my_normpath my_normpath = os.path.normpath diff --git a/src/engine/SCons/Scanner/ScannerTests.py b/src/engine/SCons/Scanner/ScannerTests.py index 4e662a2..a0c6f01 100644 --- a/src/engine/SCons/Scanner/ScannerTests.py +++ b/src/engine/SCons/Scanner/ScannerTests.py @@ -65,6 +65,17 @@ class FindPathDirsTestCase(unittest.TestCase): class ScannerTestCase(unittest.TestCase): + def test_creation(self): + """Test creation of Scanner objects through the Scanner() function""" + def func(self): + pass + s = SCons.Scanner.Scanner(func) + assert isinstance(s, SCons.Scanner.Base), s + s = SCons.Scanner.Scanner({}) + assert isinstance(s, SCons.Scanner.Selector), s + +class BaseTestCase(unittest.TestCase): + def func(self, filename, env, target, *args): self.filename = filename self.env = env @@ -192,6 +203,66 @@ class ScannerTestCase(unittest.TestCase): self.failUnless(sk == ['.3', '.4'], "sk was %s, not ['.3', '.4']") + def test_select(self): + """Test the Scanner.Base select() method""" + scanner = SCons.Scanner.Base(function = self.func) + s = scanner.select('.x') + assert s is scanner, s + +class SelectorTestCase(unittest.TestCase): + class skey: + def __init__(self, key): + self.key = key + def scanner_key(self): + return self.key + + def test___init__(self): + """Test creation of Scanner.Selector object""" + s = SCons.Scanner.Selector({}) + assert isinstance(s, SCons.Scanner.Selector), s + assert s.dict == {}, s.dict + + def test___call__(self): + """Test calling Scanner.Selector objects""" + called = [] + def s1func(node, env, path, called=called): + called.append('s1func') + called.append(node) + return [] + def s2func(node, env, path, called=called): + called.append('s2func') + called.append(node) + return [] + s1 = SCons.Scanner.Base(s1func) + s2 = SCons.Scanner.Base(s2func) + selector = SCons.Scanner.Selector({'.x' : s1, '.y' : s2}) + nx = self.skey('.x') + selector(nx, None, []) + assert called == ['s1func', nx], called + del called[:] + ny = self.skey('.y') + selector(ny, None, []) + assert called == ['s2func', ny], called + + def test_select(self): + """Test the Scanner.Selector select() method""" + selector = SCons.Scanner.Selector({'.x' : 1, '.y' : 2}) + s = selector.select(self.skey('.x')) + assert s == 1, s + s = selector.select(self.skey('.y')) + assert s == 2, s + s = selector.select(self.skey('.z')) + assert s is None, s + + def test_add_scanner(self): + """Test the Scanner.Selector add_scanner() method""" + selector = SCons.Scanner.Selector({'.x' : 1, '.y' : 2}) + s = selector.select(self.skey('.z')) + assert s is None, s + selector.add_scanner('.z', 3) + s = selector.select(self.skey('.z')) + assert s == 3, s + class CurrentTestCase(unittest.TestCase): def test_class(self): """Test the Scanner.Current class""" @@ -355,6 +426,8 @@ def suite(): tclasses = [ FindPathDirsTestCase, ScannerTestCase, + BaseTestCase, + SelectorTestCase, CurrentTestCase, ClassicTestCase, ClassicCPPTestCase, diff --git a/src/engine/SCons/Scanner/__init__.py b/src/engine/SCons/Scanner/__init__.py index 8f6c9e6..8432638 100644 --- a/src/engine/SCons/Scanner/__init__.py +++ b/src/engine/SCons/Scanner/__init__.py @@ -43,6 +43,15 @@ class _Null: # used as an actual argument value. _null = _Null +def Scanner(function, *args, **kw): + """Public interface factory function for creating different types + of Scanners based on the different types of "functions" that may + be supplied.""" + if SCons.Util.is_Dict(function): + return apply(Selector, (function,) + args, kw) + else: + return apply(Base, (function,) + args, kw) + class FindPathDirs: """A class to bind a specific *PATH variable name and the fs object to a function that will return all of the *path directories.""" @@ -190,6 +199,31 @@ class Base: return env.subst_list(self.skeys)[0] return self.skeys + def select(self, node): + return self + + +class Selector(Base): + """ + A class for selecting a more specific scanner based on the + scanner_key() (suffix) for a specific Node. + """ + def __init__(self, dict, *args, **kw): + Base.__init__(self, (None,)+args, kw) + self.dict = dict + + def __call__(self, node, env, path = ()): + return self.select(node)(node, env, path) + + def select(self, node): + try: + return self.dict[node.scanner_key()] + except KeyError: + return None + + def add_scanner(self, skey, scanner): + self.dict[skey] = scanner + class Current(Base): """ @@ -200,6 +234,7 @@ class Current(Base): def __init__(self, *args, **kw): def current_check(node, env): + calc = env.get_calculator() c = not node.has_builder() or node.current(env.get_calculator()) return c kw['scan_check'] = current_check diff --git a/src/engine/SCons/Taskmaster.py b/src/engine/SCons/Taskmaster.py index e5b3548..a253ddf 100644 --- a/src/engine/SCons/Taskmaster.py +++ b/src/engine/SCons/Taskmaster.py @@ -158,12 +158,10 @@ class Task: their dependent parent nodes. """ for t in self.targets: - def get_parents(node, parent): return node.get_parents() - 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() + # Set failure state on all of the parents that were dependent + # on this failed build. + def set_state(node): node.set_state(SCons.Node.failed) + t.call_for_all_waiting_parents(set_state) self.tm.executed(self.node) @@ -340,6 +338,12 @@ class Taskmaster: def unbuilt_nodes(node): return node.get_state() == None not_built = filter(unbuilt_nodes, derived) if not_built: + # We're waiting on one more derived files that have not + # yet been built. Add this node to the waiting_parents + # list of each of those derived files. + def add_to_waiting_parents(child, parent=node): + child.add_to_waiting_parents(parent) + map(add_to_waiting_parents, not_built) not_built.reverse() self.candidates.extend(self.order(not_built)) continue diff --git a/src/engine/SCons/TaskmasterTests.py b/src/engine/SCons/TaskmasterTests.py index 8ca7e86..7010edb 100644 --- a/src/engine/SCons/TaskmasterTests.py +++ b/src/engine/SCons/TaskmasterTests.py @@ -50,15 +50,12 @@ class Node: self.csig = None self.state = None self.prepared = None - self.parents = [] + self.waiting_parents = [] self.side_effect = 0 self.side_effects = [] self.alttargets = [] self.postprocessed = None - for kid in kids: - kid.parents.append(self) - def retrieve_from_cache(self): global cache_text if self.cached: @@ -99,15 +96,18 @@ class Node: 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 scanner_key(self): return self.name - def get_parents(self): - return self.parents + def add_to_waiting_parents(self, node): + self.waiting_parents.append(node) + + def call_for_all_waiting_parents(self, func): + func(self) + for parent in self.waiting_parents: + parent.call_for_all_waiting_parents(func) def get_state(self): return self.state @@ -569,7 +569,6 @@ class TaskmasterTestCase(unittest.TestCase): n2 = Node("n2", [n1]) n3 = Node("n3", [n2]) n1.kids = [n3] - n3.parents.append(n1) try: tm = SCons.Taskmaster.Taskmaster([n3]) diff --git a/src/engine/SCons/Tool/__init__.py b/src/engine/SCons/Tool/__init__.py index 667e085..4364cf3 100644 --- a/src/engine/SCons/Tool/__init__.py +++ b/src/engine/SCons/Tool/__init__.py @@ -104,7 +104,7 @@ def createProgBuilder(env): suffix = '$PROGSUFFIX', src_suffix = '$OBJSUFFIX', src_builder = 'Object', - scanner = SCons.Defaults.ProgScan) + target_scanner = SCons.Defaults.ProgScan) env['BUILDERS']['Program'] = program return program @@ -146,7 +146,7 @@ def createSharedLibBuilder(env): emitter = "$SHLIBEMITTER", prefix = '$SHLIBPREFIX', suffix = '$SHLIBSUFFIX', - scanner = SCons.Defaults.ProgScan, + target_scanner = SCons.Defaults.ProgScan, src_suffix = '$SHOBJSUFFIX', src_builder = 'SharedObject') env['BUILDERS']['SharedLibrary'] = shared_lib @@ -173,7 +173,8 @@ def createObjBuilders(env): emitter = "$OBJEMITTER", prefix = '$OBJPREFIX', suffix = '$OBJSUFFIX', - src_builder = ['CFile', 'CXXFile']) + src_builder = ['CFile', 'CXXFile'], + source_scanner = SCons.Defaults.ObjSourceScan) env['BUILDERS']['StaticObject'] = static_obj env['BUILDERS']['Object'] = static_obj env['OBJEMITTER'] = SCons.Defaults.StaticObjectEmitter @@ -185,7 +186,8 @@ def createObjBuilders(env): emitter = "$SHOBJEMITTER", prefix = '$SHOBJPREFIX', suffix = '$SHOBJSUFFIX', - src_builder = ['CFile', 'CXXFile']) + src_builder = ['CFile', 'CXXFile'], + source_scanner = SCons.Defaults.ObjSourceScan) env['BUILDERS']['SharedObject'] = shared_obj env['SHOBJEMITTER'] = SCons.Defaults.SharedObjectEmitter diff --git a/src/engine/SCons/Tool/mingw.py b/src/engine/SCons/Tool/mingw.py index 1369a8b..22df3d4 100644 --- a/src/engine/SCons/Tool/mingw.py +++ b/src/engine/SCons/Tool/mingw.py @@ -137,7 +137,7 @@ def generate(env): env['RCINCPREFIX'] = '--include-dir ' env['RCINCSUFFIX'] = '' env['RCCOM'] = '$RC $RCINCFLAGS $RCFLAGS -i $SOURCE -o $TARGET' - env.Append(CPPSUFFIXES = ['.rc']) + SCons.Defaults.ObjSourceScan.add_scanner('.rc', SCons.Defaults.CScan) env['BUILDERS']['RES'] = res_builder # Some setting from the platform also have to be overridden: diff --git a/src/engine/SCons/Tool/msvc.py b/src/engine/SCons/Tool/msvc.py index 3d49802..17da702 100644 --- a/src/engine/SCons/Tool/msvc.py +++ b/src/engine/SCons/Tool/msvc.py @@ -448,7 +448,7 @@ def generate(env): env['RC'] = 'rc' env['RCFLAGS'] = SCons.Util.CLVar('') env['RCCOM'] = '$RC $_CPPDEFFLAGS $_CPPINCFLAGS $RCFLAGS /fo$TARGET $SOURCES' - env.Append(CPPSUFFIXES = ['.rc']) + SCons.Defaults.ObjSourceScan.add_scanner('.rc', SCons.Defaults.CScan) env['BUILDERS']['RES'] = res_builder try: diff --git a/src/engine/SCons/Tool/qt.py b/src/engine/SCons/Tool/qt.py index f8234ba..a0dfed8 100644 --- a/src/engine/SCons/Tool/qt.py +++ b/src/engine/SCons/Tool/qt.py @@ -116,7 +116,7 @@ class _Automoc: out_sources.append(moc_o) objBuilder(moc_o, moc_cpp) self.mocFromHBld(env, moc_cpp, h) - moc_cpp.target_scanner = SCons.Defaults.CScan + #moc_cpp.target_scanner = SCons.Defaults.CScan if cpp and q_object_search.search(cpp.get_contents()): # cpp file with Q_OBJECT macro found -> add moc # (to be included in cpp) @@ -126,7 +126,7 @@ class _Automoc: env['QT_MOCNAMEGENERATOR'](base, src_ext, env))) self.mocFromCppBld(env, moc, cpp) env.Ignore(moc, moc) - moc.source_scanner = SCons.Defaults.CScan + #moc.source_scanner = SCons.Defaults.CScan os.chdir(old_os_cwd) FS.chdir(old_fs_cwd) diff --git a/test/CPPSUFFIXES.py b/test/CPPSUFFIXES.py index e64dff2..a340a4a 100644 --- a/test/CPPSUFFIXES.py +++ b/test/CPPSUFFIXES.py @@ -25,43 +25,131 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ -Test that we can add filesuffixes to $CPPSUFFIXES. +Test the ability to scan additional filesuffixes added to $CPPSUFFIXES. """ import TestSCons +python = TestSCons.python + test = TestSCons.TestSCons() +test.write('mycc.py', r""" +import string +import sys +def do_file(outf, inf): + for line in open(inf, 'rb').readlines(): + if line[:10] == '#include <': + do_file(outf, line[10:-2]) + else: + outf.write(line) +outf = open(sys.argv[1], 'wb') +for f in sys.argv[2:]: + do_file(outf, f) +sys.exit(0) +""") + test.write('SConstruct', """ -env = Environment(CPPPATH = ['.']) +env = Environment(CPPPATH = ['.'], + CC = r'%s mycc.py', + CCFLAGS = [], + CCCOM = '$CC $TARGET $SOURCES', + OBJSUFFIX = '.o') env.Append(CPPSUFFIXES = ['.x']) -env.InstallAs('foo_c', 'foo.c') -env.InstallAs('foo_x', 'foo.x') +env.Object(target = 'test1', source = 'test1.c') +env.InstallAs('test1_c', 'test1.c') +env.InstallAs('test1_h', 'test1.h') +env.InstallAs('test1_x', 'test1.x') +""" % (python,)) + +test.write('test1.c', """\ +test1.c 1 +#include <test1.h> +#include <test1.x> """) -test.write('foo.c', """\ +test.write('test1.h', """\ +test1.h 1 #include <foo.h> """) -test.write('foo.x', """\ +test.write('test1.x', """\ +test1.x 1 #include <foo.h> """) test.write('foo.h', "foo.h 1\n") test.run(arguments='.', stdout=test.wrap_stdout("""\ -Install file: "foo.c" as "foo_c" -Install file: "foo.x" as "foo_x" -""")) +%s mycc.py test1.o test1.c +Install file: "test1.c" as "test1_c" +Install file: "test1.h" as "test1_h" +Install file: "test1.x" as "test1_x" +""" % (python,))) + +test.must_match('test1.o', """\ +test1.c 1 +test1.h 1 +foo.h 1 +test1.x 1 +foo.h 1 +""") test.up_to_date(arguments='.') test.write('foo.h', "foo.h 2\n") test.run(arguments='.', stdout=test.wrap_stdout("""\ -Install file: "foo.c" as "foo_c" -Install file: "foo.x" as "foo_x" -""")) +%s mycc.py test1.o test1.c +""" % (python,))) + +test.must_match('test1.o', """\ +test1.c 1 +test1.h 1 +foo.h 2 +test1.x 1 +foo.h 2 +""") + +test.up_to_date(arguments='.') + +test.write('test1.x', """\ +test1.x 2 +#include <foo.h> +""") + +test.run(arguments='.', stdout=test.wrap_stdout("""\ +%s mycc.py test1.o test1.c +Install file: "test1.x" as "test1_x" +""" % (python,))) + +test.must_match('test1.o', """\ +test1.c 1 +test1.h 1 +foo.h 2 +test1.x 2 +foo.h 2 +""") + +test.up_to_date(arguments='.') + +test.write('test1.h', """\ +test1.h 2 +#include <foo.h> +""") + +test.run(arguments='.', stdout=test.wrap_stdout("""\ +%s mycc.py test1.o test1.c +Install file: "test1.h" as "test1_h" +""" % (python,))) + +test.must_match('test1.o', """\ +test1.c 1 +test1.h 2 +foo.h 2 +test1.x 2 +foo.h 2 +""") test.up_to_date(arguments='.') diff --git a/test/DSUFFIXES.py b/test/DSUFFIXES.py index 6f10439..188da9b 100644 --- a/test/DSUFFIXES.py +++ b/test/DSUFFIXES.py @@ -25,43 +25,131 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ -Test that we can add filesuffixes to $DSUFFIXES. +Test the ability to scan additional filesuffixes added to $DSUFFIXES. """ import TestSCons +python = TestSCons.python + test = TestSCons.TestSCons() +test.write('mydc.py', r""" +import string +import sys +def do_file(outf, inf): + for line in open(inf, 'rb').readlines(): + if line[:7] == 'import ': + do_file(outf, line[7:-2]+'.d') + else: + outf.write(line) +outf = open(sys.argv[1], 'wb') +for f in sys.argv[2:]: + do_file(outf, f) +sys.exit(0) +""") + test.write('SConstruct', """ -env = Environment(DPATH=['.']) -env.Append(DSUFFIXES = ['.x']) -env.InstallAs('foo_d', 'foo.d') -env.InstallAs('foo_x', 'foo.x') +env = Environment(DPATH = ['.'], + DC = r'%s mydc.py', + DFLAGS = [], + DCOM = '$DC $TARGET $SOURCES', + OBJSUFFIX = '.o') +env.Append(CPPSUFFIXES = ['.x']) +env.Object(target = 'test1', source = 'test1.d') +env.InstallAs('test1_d', 'test1.d') +env.InstallAs('test2_d', 'test2.d') +env.InstallAs('test3_d', 'test3.d') +""" % (python,)) + +test.write('test1.d', """\ +test1.d 1 +import test2; +import test3; +""") + +test.write('test2.d', """\ +test2.d 1 +import foo; """) -test.write('foo.d', """\ -import inc; +test.write('test3.d', """\ +test3.d 1 +import foo; """) -test.write('foo.x', """\ -import inc; +test.write('foo.d', "foo.d 1\n") + +test.run(arguments='.', stdout=test.wrap_stdout("""\ +%s mydc.py test1.o test1.d +Install file: "test1.d" as "test1_d" +Install file: "test2.d" as "test2_d" +Install file: "test3.d" as "test3_d" +""" % (python,))) + +test.must_match('test1.o', """\ +test1.d 1 +test2.d 1 +foo.d 1 +test3.d 1 +foo.d 1 +""") + +test.up_to_date(arguments='.') + +test.write('foo.d', "foo.d 2\n") + +test.run(arguments='.', stdout=test.wrap_stdout("""\ +%s mydc.py test1.o test1.d +""" % (python,))) + +test.must_match('test1.o', """\ +test1.d 1 +test2.d 1 +foo.d 2 +test3.d 1 +foo.d 2 """) -test.write('inc.d', "inc.d 1\n") +test.up_to_date(arguments='.') + +test.write('test3.d', """\ +test3.d 2 +import foo; +""") test.run(arguments='.', stdout=test.wrap_stdout("""\ -Install file: "foo.d" as "foo_d" -Install file: "foo.x" as "foo_x" -""")) +%s mydc.py test1.o test1.d +Install file: "test3.d" as "test3_d" +""" % (python,))) + +test.must_match('test1.o', """\ +test1.d 1 +test2.d 1 +foo.d 2 +test3.d 2 +foo.d 2 +""") test.up_to_date(arguments='.') -test.write('inc.d', "inc 2\n") +test.write('test2.d', """\ +test2.d 2 +import foo; +""") test.run(arguments='.', stdout=test.wrap_stdout("""\ -Install file: "foo.d" as "foo_d" -Install file: "foo.x" as "foo_x" -""")) +%s mydc.py test1.o test1.d +Install file: "test2.d" as "test2_d" +""" % (python,))) + +test.must_match('test1.o', """\ +test1.d 1 +test2.d 2 +foo.d 2 +test3.d 2 +foo.d 2 +""") test.up_to_date(arguments='.') diff --git a/test/FORTRANSUFFIXES.py b/test/FORTRANSUFFIXES.py index 1ab7306..c5047f7 100644 --- a/test/FORTRANSUFFIXES.py +++ b/test/FORTRANSUFFIXES.py @@ -25,43 +25,135 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ -Test that we can add filesuffixes to $FORTRANSUFFIXES. +Test the ability to scan additional filesuffixes added to $FORTRANSUFFIXES. """ import TestSCons +python = TestSCons.python + test = TestSCons.TestSCons() +test.write('myfc.py', r""" +import string +import sys +def do_file(outf, inf): + for line in open(inf, 'rb').readlines(): + if line[:15] == " INCLUDE '": + do_file(outf, line[15:-2]) + else: + outf.write(line) +outf = open(sys.argv[1], 'wb') +for f in sys.argv[2:]: + do_file(outf, f) +sys.exit(0) +""") + test.write('SConstruct', """ -env = Environment() +env = Environment(F77PATH = ['.'], + F77 = r'%s myfc.py', + F77FLAGS = [], + F77COM = '$F77 $TARGET $SOURCES', + OBJSUFFIX = '.o') env.Append(FORTRANSUFFIXES = ['.x']) -env.InstallAs('foo_f', 'foo.f') -env.InstallAs('foo_x', 'foo.x') +env.Object(target = 'test1', source = 'test1.f') +env.InstallAs('test1_f', 'test1.f') +env.InstallAs('test1_h', 'test1.h') +env.InstallAs('test1_x', 'test1.x') +""" % (python,)) + +test.write('test1.f', """\ + test1.f 1 + INCLUDE 'test1.h' + INCLUDE 'test1.x' +""") + +test.write('test1.h', """\ + test1.h 1 + INCLUDE 'foo.h' +""") + +test.write('test1.x', """\ + test1.x 1 + INCLUDE 'foo.h' +""") + +test.write('foo.h', """\ + foo.h 1 +""") + +test.run(arguments='.', stdout=test.wrap_stdout("""\ +%s myfc.py test1.o test1.f +Install file: "test1.f" as "test1_f" +Install file: "test1.h" as "test1_h" +Install file: "test1.x" as "test1_x" +""" % (python,))) + +test.must_match('test1.o', """\ + test1.f 1 + test1.h 1 + foo.h 1 + test1.x 1 + foo.h 1 """) -test.write('foo.f', """\ -INCLUDE 'foo.h' +test.up_to_date(arguments='.') + +test.write('foo.h', """\ + foo.h 2 """) -test.write('foo.x', """\ -INCLUDE 'foo.h' +test.run(arguments='.', stdout=test.wrap_stdout("""\ +%s myfc.py test1.o test1.f +""" % (python,))) + +test.must_match('test1.o', """\ + test1.f 1 + test1.h 1 + foo.h 2 + test1.x 1 + foo.h 2 """) -test.write('foo.h', "foo.h 1\n") +test.up_to_date(arguments='.') + +test.write('test1.x', """\ + test1.x 2 + INCLUDE 'foo.h' +""") test.run(arguments='.', stdout=test.wrap_stdout("""\ -Install file: "foo.f" as "foo_f" -Install file: "foo.x" as "foo_x" -""")) +%s myfc.py test1.o test1.f +Install file: "test1.x" as "test1_x" +""" % (python,))) + +test.must_match('test1.o', """\ + test1.f 1 + test1.h 1 + foo.h 2 + test1.x 2 + foo.h 2 +""") test.up_to_date(arguments='.') -test.write('foo.h', "foo.h 2\n") +test.write('test1.h', """\ + test1.h 2 + INCLUDE 'foo.h' +""") test.run(arguments='.', stdout=test.wrap_stdout("""\ -Install file: "foo.f" as "foo_f" -Install file: "foo.x" as "foo_x" -""")) +%s myfc.py test1.o test1.f +Install file: "test1.h" as "test1_h" +""" % (python,))) + +test.must_match('test1.o', """\ + test1.f 1 + test1.h 2 + foo.h 2 + test1.x 2 + foo.h 2 +""") test.up_to_date(arguments='.') diff --git a/test/HeaderInstall.py b/test/HeaderInstall.py index 523044c..cccf4d6 100644 --- a/test/HeaderInstall.py +++ b/test/HeaderInstall.py @@ -27,11 +27,15 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" Test that dependencies in installed header files get re-scanned correctly. """ +import os.path + import TestSCons test = TestSCons.TestSCons() -test.write('SConstruct', """ +test.subdir('work1', ['work1', 'dist']) + +test.write(['work1', 'SConstruct'], """ env = Environment(CPPPATH=['#include']) Export('env') SConscript('dist/SConscript') @@ -39,31 +43,72 @@ libfoo = env.StaticLibrary('foo', ['foo.c']) Default(libfoo) """) -test.write('foo.c', """ +test.write(['work1', 'foo.c'], """ #include <h1.h> """) -test.subdir('dist') - -test.write(['dist', 'SConscript'], """\ +test.write(['work1', 'dist', 'SConscript'], """\ Import('env') env.Install('#include', ['h1.h', 'h2.h', 'h3.h']) """) -test.write(['dist', 'h1.h'], """\ +test.write(['work1', 'dist', 'h1.h'], """\ #include "h2.h" """) -test.write(['dist', 'h2.h'], """\ +test.write(['work1', 'dist', 'h2.h'], """\ #include "h3.h" """) -test.write(['dist', 'h3.h'], """\ +test.write(['work1', 'dist', 'h3.h'], """\ int foo = 3; """) -test.run(arguments = ".") +test.run(chdir = 'work1', arguments = ".") + +test.up_to_date(chdir = 'work1', arguments = ".") + +# +test.subdir('ref', 'work2', ['work2', 'src']) + +test.write(['work2', 'SConstruct'], """ +env = Environment(CPPPATH=['build', r'%s']) +env.Install('build', 'src/in1.h') +env.Install('build', 'src/in2.h') +env.Install('build', 'src/in3.h') +""" % test.workpath('ref')) + +test.write(['ref', 'in1.h'], '#define FILE "ref/in1.h"\n#include <in2.h>\n') +test.write(['ref', 'in2.h'], '#define FILE "ref/in2.h"\n#include <in3.h>\n') +test.write(['ref', 'in3.h'], '#define FILE "ref/in3.h"\n#define FOO 0\n') + +src_in1_h = '#define FILE "src/in1.h"\n#include <in2.h>\n' +src_in2_h = '#define FILE "src/in2.h"\n#include <in3.h>\n' +src_in3_h = '#define FILE "src/in3.h"\n#define FOO 0\n' +test.write(['work2', 'src', 'in1.h'], src_in1_h) +test.write(['work2', 'src', 'in2.h'], src_in2_h) +test.write(['work2', 'src', 'in3.h'], src_in3_h) + +test.run(chdir = 'work2', arguments = 'build') + +test.must_match(['work2', 'build', 'in1.h'], src_in1_h) +test.must_match(['work2', 'build', 'in2.h'], src_in2_h) +test.must_match(['work2', 'build', 'in3.h'], src_in3_h) + +test.up_to_date(chdir = 'work2', arguments = 'build') + +src_in3_h = '#define FILE "src/in3.h"\n#define FOO 1\n' +test.write(['work2', 'src', 'in3.h'], src_in3_h) + +test.run(chdir = 'work2', arguments = 'build', stdout=test.wrap_stdout("""\ +Install file: "%s" as "%s" +""" % (os.path.join('src', 'in3.h'), + os.path.join('build', 'in3.h')))) + +test.must_match(['work2', 'build', 'in1.h'], src_in1_h) +test.must_match(['work2', 'build', 'in2.h'], src_in2_h) +test.must_match(['work2', 'build', 'in3.h'], src_in3_h) -test.up_to_date(arguments = ".") +test.up_to_date(chdir = 'work2', arguments = 'build') test.pass_test() diff --git a/test/Scanner.py b/test/Scanner.py index 61d9e39..0bb3d8b 100644 --- a/test/Scanner.py +++ b/test/Scanner.py @@ -91,9 +91,8 @@ env2 = env.Copy() env2.Append(SCANNERS = [k2scan]) env2.Command('junk', 'junk.k2', r'%s build.py $SOURCES $TARGET') -bar_in = File('bar.in') -env.Command('bar', bar_in, r'%s build.py $SOURCES $TARGET') -bar_in.source_scanner = kscan +bar = env.Command('bar', 'bar.in', r'%s build.py $SOURCES $TARGET') +bar.source_scanner = kscan """ % (python, python, python)) test.write('foo.k', diff --git a/test/option--warn.py b/test/option--warn.py index 41a37e1..8c84b6d 100644 --- a/test/option--warn.py +++ b/test/option--warn.py @@ -54,12 +54,15 @@ test = TestSCons.TestSCons(match = TestCmd.match_re_dotall) -test.write("SConstruct",""" +test.write("SConstruct", """\ +import SCons.Defaults + def build(target, source, env): pass env=Environment() -env['BUILDERS']['test'] = Builder(action=build) +env['BUILDERS']['test'] = Builder(action=build, + source_scanner=SCons.Defaults.ObjSourceScan) env.test(target='foo', source='foo.c') """) diff --git a/test/scan-once.py b/test/scan-once.py index 52b4505..606590b 100644 --- a/test/scan-once.py +++ b/test/scan-once.py @@ -110,38 +110,6 @@ builders["StaticLibMerge"] = BStaticLibMerge env = Environment(BUILDERS = builders) e = env.Dictionary() # Slightly easier to type -Scanned = {} - -def write_out(file, dict): - keys = dict.keys() - keys.sort() - f = open(file, 'wb') - for k in keys: - file = os.path.split(k)[1] - f.write(file + ": " + str(dict[k]) + "\\n") - f.close() - -import SCons.Scanner.C -c_scanner = SCons.Scanner.C.CScan() -def MyCScan(node, env, target): - deps = c_scanner(node, env, target) - - global Scanned - n = str(node) - try: - Scanned[n] = Scanned[n] + 1 - except KeyError: - Scanned[n] = 1 - write_out('MyCScan.out', Scanned) - - return deps -S_MyCScan = SCons.Scanner.Current(skeys = [".c", ".C", ".cxx", ".cpp", ".c++", ".cc", - ".h", ".H", ".hxx", ".hpp", ".h++", ".hh"], - function = MyCScan, - recursive = 1) -# QQQ Yes, this is manner of fixing the SCANNERS list is fragile. -env["SCANNERS"] = [S_MyCScan] + env["SCANNERS"][1:] - global_env = env e["GlobalEnv"] = global_env @@ -167,7 +135,6 @@ test.write(['SLF', 'Mylib.py'], """\ import os import string import re -import SCons.Environment def Subdirs(env, dirlist): for file in _subconf_list(dirlist): @@ -376,10 +343,40 @@ for h in ['libg_gx.h', 'libg_gy.h', 'libg_gz.h']: """) test.write(['SLF', 'src', 'lib_geng', 'SConstruct'], """\ +import os + +Scanned = {} + +def write_out(file, dict): + keys = dict.keys() + keys.sort() + f = open(file, 'wb') + for k in keys: + file = os.path.split(k)[1] + f.write(file + ": " + str(dict[k]) + "\\n") + f.close() + +orig_function = CScan.function + +def MyCScan(node, env, target, orig_function=orig_function): + deps = orig_function(node, env, target) + + global Scanned + n = str(node) + try: + Scanned[n] = Scanned[n] + 1 + except KeyError: + Scanned[n] = 1 + write_out(r'%s', Scanned) + + return deps + +CScan.function = MyCScan + env = Environment(CPPPATH = ".") l = env.StaticLibrary("g", Split("libg_1.c libg_2.c libg_3.c")) Default(l) -""") +""" % test.workpath('MyCScan.out')) # These were the original shell script and Makefile from SLF's original # bug report. We're not using them--in order to make this script as |