From: stevenknight Date: Wed, 19 May 2004 17:49:55 +0000 (+0000) Subject: Fix spurious rebuilds/reinstalls of header files and circular dependencies with gener... X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=d87dac2e6d4a89a587c2764912a8293a75e5a285;p=scons.git 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. git-svn-id: http://scons.tigris.org/svn/scons/trunk@980 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- diff --git a/doc/man/scons.1 b/doc/man/scons.1 index 88e5b471..ea0cf811 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 b1d31198..7aaabf5d 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 5c75a0f8..4aa518f3 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 e33d95c0..e592250b 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 7a47b215..4c42f281 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 b9206438..d43a99f7 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 15434cbf..0d158a18 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 fb2e0fa7..f69dd4e1 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 535c84e0..e36d6ce8 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 28417597..75b3a6dc 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 db4e375d..196b426f 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 8bc11ad5..39af3a18 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 f63060a3..693ec18d 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 e69f99e7..766f0ee6 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 c20dedc3..876f4920 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 4e662a2a..a0c6f01f 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 8f6c9e6a..8432638e 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 e5b35483..a253ddfa 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 8ca7e86f..7010edbc 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 667e0853..4364cf3e 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 1369a8bb..22df3d4a 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 3d498026..17da7023 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 f8234ba1..a0dfed88 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 e64dff27..a340a4a0 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 +#include """) -test.write('foo.c', """\ +test.write('test1.h', """\ +test1.h 1 #include """) -test.write('foo.x', """\ +test.write('test1.x', """\ +test1.x 1 #include """) 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 +""") + +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 +""") + +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 6f10439c..188da9bd 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 1ab73062..c5047f7b 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 523044cf..cccf4d6b 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 """) -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 \n') +test.write(['ref', 'in2.h'], '#define FILE "ref/in2.h"\n#include \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 \n' +src_in2_h = '#define FILE "src/in2.h"\n#include \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 61d9e391..0bb3d8bb 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 41a37e1a..8c84b6da 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 52b45050..606590b2 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