diff options
author | Steven Knight <knight@baldmt.com> | 2004-04-17 12:34:55 (GMT) |
---|---|---|
committer | Steven Knight <knight@baldmt.com> | 2004-04-17 12:34:55 (GMT) |
commit | 6afc74928b529087a998c8cb036f471f0a2497c9 (patch) | |
tree | e89b0387d2448f33c7a1dcf1cc47d12f584787fa | |
parent | cf66aef7008b81abfb4e126e99cef44a29dbda10 (diff) | |
download | SCons-6afc74928b529087a998c8cb036f471f0a2497c9.zip SCons-6afc74928b529087a998c8cb036f471f0a2497c9.tar.gz SCons-6afc74928b529087a998c8cb036f471f0a2497c9.tar.bz2 |
Add the highly anticipated --debug=explain option to provide build reasoning.
-rw-r--r-- | src/engine/SCons/Action.py | 14 | ||||
-rw-r--r-- | src/engine/SCons/ActionTests.py | 73 | ||||
-rw-r--r-- | src/engine/SCons/Executor.py | 10 | ||||
-rw-r--r-- | src/engine/SCons/ExecutorTests.py | 10 | ||||
-rw-r--r-- | src/engine/SCons/Node/FS.py | 16 | ||||
-rw-r--r-- | src/engine/SCons/Node/FSTests.py | 10 | ||||
-rw-r--r-- | src/engine/SCons/Node/NodeTests.py | 38 | ||||
-rw-r--r-- | src/engine/SCons/Node/__init__.py | 45 | ||||
-rw-r--r-- | src/engine/SCons/Script/__init__.py | 73 | ||||
-rw-r--r-- | src/engine/SCons/Sig/SigTests.py | 32 | ||||
-rw-r--r-- | src/engine/SCons/Sig/__init__.py | 37 | ||||
-rw-r--r-- | test/explain.py | 295 |
12 files changed, 597 insertions, 56 deletions
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index f7024d3..7397873 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -29,6 +29,14 @@ other modules: This is what the Sig/*.py subsystem uses to decide if a target needs to be rebuilt because its action changed. + genstring() + Returns a string representation of the Action *without* command + substitution, but allows a CommandGeneratorAction to generate + the right action based on the specified target, source and env. + This is used by the Signature subsystem (through the Executor) + to compare the actions used to build a target last time and + this time. + Subclasses also supply the following methods for internal use within this module: @@ -215,6 +223,9 @@ class ActionBase: (string.join(map(lambda x: str(x), target), ' and '), string.join(lines, '\n '))) + def genstring(self, target, source, env): + return str(self) + def get_actions(self): return [self] @@ -384,6 +395,9 @@ class CommandGeneratorAction(ActionBase): act = self.__generate([], [], env, 0) return str(act) + def genstring(self, target, source, env): + return str(self.__generate(target, source, env, 0)) + def _execute(self, target, source, env): if not SCons.Util.is_List(source): source = [source] diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index 5672dac..a273607 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -465,6 +465,44 @@ class CommandActionTestCase(unittest.TestCase): s = str(act) assert s == "['xyzzy', '$TARGET', '$SOURCE', '$TARGETS', '$SOURCES']", s + def test_genstring(self): + """Test the genstring() method for command Actions + """ + + env = Environment() + t1 = DummyNode('t1') + t2 = DummyNode('t2') + s1 = DummyNode('s1') + s2 = DummyNode('s2') + act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE') + expect = 'xyzzy $TARGET $SOURCE' + s = act.genstring([], [], env) + assert s == expect, s + s = act.genstring([t1], [s1], env) + assert s == expect, s + s = act.genstring([t1, t2], [s1, s2], env) + assert s == expect, s + + act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES') + expect = 'xyzzy $TARGETS $SOURCES' + s = act.genstring([], [], env) + assert s == expect, s + s = act.genstring([t1], [s1], env) + assert s == expect, s + s = act.genstring([t1, t2], [s1, s2], env) + assert s == expect, s + + act = SCons.Action.CommandAction(['xyzzy', + '$TARGET', '$SOURCE', + '$TARGETS', '$SOURCES']) + expect = "['xyzzy', '$TARGET', '$SOURCE', '$TARGETS', '$SOURCES']" + s = act.genstring([], [], env) + assert s == expect, s + s = act.genstring([t1], [s1], env) + assert s == expect, s + s = act.genstring([t1, t2], [s1, s2], env) + assert s == expect, s + def test_strfunction(self): """Test fetching the string representation of command Actions """ @@ -829,6 +867,19 @@ class CommandGeneratorActionTestCase(unittest.TestCase): s = str(a) assert s == 'FOO', s + def test_genstring(self): + """Test the command generator Action genstring() method + """ + def f(target, source, env, for_signature, self=self): + dummy = env['dummy'] + self.dummy = dummy + return "$FOO $TARGET $SOURCE $TARGETS $SOURCES" + a = SCons.Action.CommandGeneratorAction(f) + self.dummy = 0 + s = a.genstring([], [], env=Environment(FOO='xyzzy', dummy=1)) + assert self.dummy == 1, self.dummy + assert s == "$FOO $TARGET $SOURCE $TARGETS $SOURCES", s + def test_strfunction(self): """Test the command generator Action string function """ @@ -1079,7 +1130,7 @@ class ListActionTestCase(unittest.TestCase): assert g == [l[1]], g def test___str__(self): - """Test the __str__() method ffor a list of subsidiary Actions + """Test the __str__() method for a list of subsidiary Actions """ def f(target,source,env): pass @@ -1089,6 +1140,17 @@ class ListActionTestCase(unittest.TestCase): s = str(a) assert s == "f(env, target, source)\ng(env, target, source)\nXXX\nf(env, target, source)", s + def test_genstring(self): + """Test the genstring() method for a list of subsidiary Actions + """ + def f(target,source,env): + pass + def g(target,source,env): + pass + a = SCons.Action.ListAction([f, g, "XXX", f]) + s = a.genstring([], [], Environment()) + assert s == "f(env, target, source)\ng(env, target, source)\nXXX\nf(env, target, source)", s + def test_strfunction(self): """Test the string function for a list of subsidiary Actions """ @@ -1173,6 +1235,15 @@ class LazyActionTestCase(unittest.TestCase): s = a.strfunction([], [], env=Environment(BAR=f, s=self)) assert s == "f([], [])", s + def test_genstring(self): + """Test the lazy-evaluation Action genstring() method + """ + def f(target, source, env): + pass + a = SCons.Action.Action('$BAR') + s = a.genstring([], [], env=Environment(BAR=f, s=self)) + assert s == "f(env, target, source)", s + def test_execute(self): """Test executing a lazy-evaluation Action """ diff --git a/src/engine/SCons/Executor.py b/src/engine/SCons/Executor.py index b636f60..ac209e0 100644 --- a/src/engine/SCons/Executor.py +++ b/src/engine/SCons/Executor.py @@ -133,6 +133,16 @@ class Executor: slist = filter(lambda x, s=self.sources: x not in s, sources) self.sources.extend(slist) + def __str__(self): + try: + return self.string + except AttributeError: + action = self.builder.action + self.string = action.genstring(self.targets, + self.sources, + self.get_build_env()) + return self.string + def get_raw_contents(self): """Fetch the raw signature contents. This, along with get_contents(), is the real reason this class exists, so we can diff --git a/src/engine/SCons/ExecutorTests.py b/src/engine/SCons/ExecutorTests.py index 9c64012..23b7719 100644 --- a/src/engine/SCons/ExecutorTests.py +++ b/src/engine/SCons/ExecutorTests.py @@ -47,6 +47,8 @@ class MyAction: actions = ['action1', 'action2'] def get_actions(self): return self.actions + def genstring(self, target, source, env): + return string.join(['GENSTRING'] + self.actions + target + source) def get_raw_contents(self, target, source, env): return string.join(['RAW'] + self.actions + target + source) def get_contents(self, target, source, env): @@ -165,6 +167,14 @@ class ExecutorTestCase(unittest.TestCase): x.add_sources(['s3', 's1', 's4']) assert x.sources == ['s1', 's2', 's3', 's4'], x.sources + def test___str__(self): + """Test the __str__() method""" + env = MyEnvironment(S='string') + + x = SCons.Executor.Executor(MyBuilder(env, {}), None, {}, ['t'], ['s']) + c = str(x) + assert c == 'GENSTRING action1 action2 t s', c + def test_get_raw_contents(self): """Test fetching the raw signatures contents""" env = MyEnvironment(RC='raw contents') diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index bb13afc..a349f77 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -1225,13 +1225,13 @@ class Dir(Base): """A directory does not get scanned.""" return None - def set_bsig(self, bsig): + def set_binfo(self, bsig, bkids, bkidsigs, bact, bactsig): """A directory has no signature.""" - bsig = None + pass def set_csig(self, csig): """A directory has no signature.""" - csig = None + pass def get_contents(self): """Return aggregate contents of all our children.""" @@ -1363,8 +1363,12 @@ class File(Base): def store_csig(self): self.dir.sconsign().set_csig(self.name, self.get_csig()) - def store_bsig(self): - self.dir.sconsign().set_bsig(self.name, self.get_bsig()) + def store_binfo(self): + binfo = self.get_binfo() + apply(self.dir.sconsign().set_binfo, (self.name,) + binfo) + + def get_stored_binfo(self): + return self.dir.sconsign().get_binfo(self.name) def store_implicit(self): self.dir.sconsign().set_implicit(self.name, self.implicit) @@ -1613,7 +1617,7 @@ class File(Base): # ...and they'd like a local copy. LocalCopy(self, r, None) self.set_bsig(bsig) - self.store_bsig() + self.store_binfo() return 1 self._rfile = self return None diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py index b1999e0..6fbcfdc 100644 --- a/src/engine/SCons/Node/FSTests.py +++ b/src/engine/SCons/Node/FSTests.py @@ -815,7 +815,7 @@ class FSTestCase(unittest.TestCase): e8 = fs.Entry("e8") assert e8.get_bsig() is None, e8.get_bsig() assert e8.get_csig() is None, e8.get_csig() - e8.set_bsig('xxx') + e8.set_binfo('xxx', [], [], [], []) e8.set_csig('yyy') assert e8.get_bsig() == 'xxx', e8.get_bsig() assert e8.get_csig() == 'yyy', e8.get_csig() @@ -823,7 +823,7 @@ class FSTestCase(unittest.TestCase): f9 = fs.File("f9") assert f9.get_bsig() is None, f9.get_bsig() assert f9.get_csig() is None, f9.get_csig() - f9.set_bsig('xxx') + f9.set_binfo('xxx', [], [], [], []) f9.set_csig('yyy') assert f9.get_bsig() == 'xxx', f9.get_bsig() assert f9.get_csig() == 'yyy', f9.get_csig() @@ -831,7 +831,7 @@ class FSTestCase(unittest.TestCase): d10 = fs.Dir("d10") assert d10.get_bsig() is None, d10.get_bsig() assert d10.get_csig() is None, d10.get_csig() - d10.set_bsig('xxx') + d10.set_binfo('xxx', [], [], [], []) d10.set_csig('yyy') assert d10.get_bsig() is None, d10.get_bsig() assert d10.get_csig() is None, d10.get_csig() @@ -1651,7 +1651,7 @@ class CacheDirTestCase(unittest.TestCase): SCons.Sig.MD5.collect = my_collect try: f5 = fs.File("cd.f5") - f5.set_bsig('a_fake_bsig') + f5.set_binfo('a_fake_bsig', [], [], [], []) cp = f5.cachepath() dirname = os.path.join('cache', 'A') filename = os.path.join(dirname, 'a_fake_bsig') @@ -1661,7 +1661,7 @@ class CacheDirTestCase(unittest.TestCase): # Verify that no bsig raises an InternalERror f6 = fs.File("cd.f6") - f6.set_bsig(None) + f6.set_binfo(None, [], [], [], []) exc_caught = 0 try: cp = f6.cachepath() diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py index 63b945f..e9d779c 100644 --- a/src/engine/SCons/Node/NodeTests.py +++ b/src/engine/SCons/Node/NodeTests.py @@ -344,18 +344,34 @@ class NodeTestCase(unittest.TestCase): a = node.builder.get_actions() assert isinstance(a[0], MyAction), a[0] - def test_set_bsig(self): - """Test setting a Node's signature + def test_set_binfo(self): + """Test setting a Node's build information + """ + node = SCons.Node.Node() + node.set_binfo('www', ['w1'], ['w2'], 'w act', 'w actsig') + assert node.bsig == 'www', node.bsig + assert node.bkids == ['w1'], node.bkdids + assert node.bkidsigs == ['w2'], node.bkidsigs + assert node.bact == 'w act', node.bkdid + assert node.bactsig == 'w actsig', node.bkidsig + + def test_get_binfo(self): + """Test fetching a Node's build information """ node = SCons.Node.Node() - node.set_bsig('www') - assert node.bsig == 'www' + node.set_binfo('yyy', ['y1'], ['y2'], 'y act', 'y actsig') + bsig, bkids, bkidsigs, bact, bactsig = node.get_binfo() + assert bsig == 'yyy', bsig + assert bkids == ['y1'], bkdids + assert bkidsigs == ['y2'], bkidsigs + assert bact == 'y act', bkdid + assert bactsig == 'y actsig', bkidsig def test_get_bsig(self): """Test fetching a Node's signature """ node = SCons.Node.Node() - node.set_bsig('xxx') + node.set_binfo('xxx', ['x1'], ['x2'], 'x act', 'x actsig') assert node.get_bsig() == 'xxx' def test_set_csig(self): @@ -372,11 +388,11 @@ class NodeTestCase(unittest.TestCase): node.set_csig('zzz') assert node.get_csig() == 'zzz' - def test_store_bsig(self): - """Test calling the method to store a build signature + def test_store_binfo(self): + """Test calling the method to store build information """ node = SCons.Node.Node() - node.store_bsig() + node.store_binfo() def test_store_csig(self): """Test calling the method to store a content signature @@ -907,7 +923,7 @@ class NodeTestCase(unittest.TestCase): n = SCons.Node.Node() n.set_state(3) - n.set_bsig('bsig') + n.set_binfo('bbb', ['b1'], ['b2'], 'b act', 'b actsig') n.set_csig('csig') n.includes = 'testincludes' n.found_include = {'testkey':'testvalue'} @@ -917,6 +933,10 @@ class NodeTestCase(unittest.TestCase): assert n.get_state() is None, n.get_state() assert not hasattr(n, 'bsig'), n.bsig + assert not hasattr(n, 'bkids'), n.bkids + assert not hasattr(n, 'bkidsigs'), n.bkidsigs + assert not hasattr(n, 'bact'), n.bact + assert not hasattr(n, 'bactsig'), n.bactsig assert not hasattr(n, 'csig'), n.csig assert n.includes is None, n.includes assert n.found_includes == {}, n.found_includes diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index 861b554..a9581e4 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -205,7 +205,7 @@ class Node: def built(self): """Called just after this node is sucessfully built.""" - self.store_bsig() + self.store_binfo() # Clear out the implicit dependency caches: # XXX this really should somehow be made more general and put @@ -217,7 +217,7 @@ class Node: def get_parents(node, parent): return node.get_parents() def clear_cache(node, parent): node.implicit = None - node.del_bsig() + node.del_binfo() w = Walker(self, get_parents, ignore_cycle, clear_cache) while w.next(): pass @@ -241,7 +241,7 @@ class Node: builds). """ self.set_state(None) - self.del_bsig() + self.del_binfo() self.del_csig() try: delattr(self, '_calculated_sig') @@ -402,7 +402,7 @@ class Node: self.implicit = [] self.implicit_dict = {} self._children_reset() - self.del_bsig() + self.del_binfo() build_env = self.get_build_env() @@ -495,22 +495,47 @@ class Node: """Set the node's build signature (based on the signatures of its dependency files and build information).""" self.bsig = bsig + + def get_binfo(self): + """Get the node's build signature (based on the signatures + of its dependency files and build information).""" + result = [] + for attr in ['bsig', 'bkids', 'bkidsigs', 'bact', 'bactsig']: + try: + r = getattr(self, attr) + except AttributeError: + r = None + result.append(r) + return tuple(result) + + def set_binfo(self, bsig, bkids, bkidsigs, bact, bactsig): + """Set the node's build signature (based on the signatures + of its dependency files and build information).""" + self.bsig = bsig + self.bkids = bkids + self.bkidsigs = bkidsigs + self.bact = bact + self.bactsig = bactsig try: delattr(self, '_tempbsig') except AttributeError: pass - def store_bsig(self): + def store_binfo(self): """Make the build signature permanent (that is, store it in the .sconsign file or equivalent).""" pass - def del_bsig(self): + def get_stored_binfo(self): + return (None, None, None, None, None) + + def del_binfo(self): """Delete the bsig from this node.""" - try: - delattr(self, 'bsig') - except AttributeError: - pass + for attr in ['bsig', 'bkids', 'bkidsigs', 'bact', 'bactsig']: + try: + delattr(self, attr) + except AttributeError: + pass def get_csig(self): """Get the signature of the node's content.""" diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py index cac2fcf..245fea9 100644 --- a/src/engine/SCons/Script/__init__.py +++ b/src/engine/SCons/Script/__init__.py @@ -70,6 +70,18 @@ import SCons.Util import SCons.Warnings # +import __builtin__ +try: + __builtin__.zip +except AttributeError: + def zip(l1, l2): + result = [] + for i in xrange(len(l1)): + result.append((l1[i], l2[i])) + return result + __builtin__.zip = zip + +# display = SCons.Util.display progress_display = SCons.Util.DisplayEngine() @@ -175,6 +187,58 @@ class BuildTask(SCons.Taskmaster.Task): self.do_failed(status) + def make_ready(self): + """Make a task ready for execution""" + SCons.Taskmaster.Task.make_ready(self) + if self.out_of_date and print_explanations: + node = self.out_of_date[0] + if not node.exists(): + sys.stdout.write("scons: building `%s' because it doesn't exist\n" % node) + return + + oldbsig, oldkids, oldsigs, oldact, oldactsig = node.get_stored_binfo() + if oldkids is None: + return + + def dictify(kids, sigs): + result = {} + for k, s in zip(kids, sigs): + result[k] = s + return result + + osig = dictify(oldkids, oldsigs) + + newkids, newsigs = map(str, node.bkids), node.bkidsigs + nsig = dictify(newkids, newsigs) + + lines = map(lambda x: "`%s' is no longer a dependency\n" % x, + filter(lambda x, nk=newkids: not x in nk, oldkids)) + + for k in newkids: + if not k in oldkids: + lines.append("`%s' is a new dependency\n" % k) + elif osig[k] != nsig[k]: + lines.append("`%s' changed\n" % k) + + if len(lines) == 0: + newact, newactsig = node.bact, node.bactsig + if oldact != newact: + lines.append("the build action changed:\n" + + "%sold: %s\n" % (' '*15, oldact) + + "%snew: %s\n" % (' '*15, newact)) + + if len(lines) == 0: + lines.append("the dependency order changed:\n" + + "%sold: %s\n" % (' '*15, oldkids) + + "%snew: %s\n" % (' '*15, newkids)) + + preamble = "scons: rebuilding `%s' because" % node + if len(lines) == 1: + sys.stdout.write("%s %s" % (preamble, lines[0])) + else: + lines = ["%s:\n" % preamble] + lines + sys.stdout.write(string.join(lines, ' '*11)) + class CleanTask(SCons.Taskmaster.Task): """An SCons clean task.""" def show(self): @@ -225,6 +289,7 @@ class QuestionTask(SCons.Taskmaster.Task): keep_going_on_error = 0 print_count = 0 print_dtree = 0 +print_explanations = 0 print_includes = 0 print_objects = 0 print_time = 0 @@ -389,7 +454,8 @@ def _SConstruct_exists(dirname=''): def _set_globals(options): global repositories, keep_going_on_error, ignore_errors - global print_count, print_dtree, print_includes + global print_count, print_dtree + global print_explanations, print_includes global print_objects, print_time, print_tree global memory_outf, memory_stats @@ -402,6 +468,8 @@ def _set_globals(options): print_count = 1 elif options.debug == "dtree": print_dtree = 1 + elif options.debug == "explain": + print_explanations = 1 elif options.debug == "includes": print_includes = 1 elif options.debug == "memory": @@ -493,7 +561,8 @@ class OptParser(OptionParser): help="Search up directory tree for SConstruct, " "build all Default() targets.") - debug_options = ["count", "dtree", "includes", "memory", "objects", + debug_options = ["count", "dtree", "explain", + "includes", "memory", "objects", "pdb", "presub", "time", "tree"] def opt_debug(option, opt, value, parser, debug_options=debug_options): diff --git a/src/engine/SCons/Sig/SigTests.py b/src/engine/SCons/Sig/SigTests.py index f297464..98465ff 100644 --- a/src/engine/SCons/Sig/SigTests.py +++ b/src/engine/SCons/Sig/SigTests.py @@ -106,8 +106,12 @@ class DummyNode: else: return calc.csig(self) - def set_bsig(self, bsig): + def set_binfo(self, bsig, bkids, bkidsigs, bact, bactsig): self.bsig = bsig + self.bkids = bkids + self.bkidsigs = bkidsigs + self.bact = bact + self.bactsig = bactsig def get_bsig(self): return self.bsig @@ -129,12 +133,6 @@ class DummyNode: def get_stored_implicit(self): return None - - def store_csig(self): - pass - - def store_bsig(self): - pass def store_timestamp(self): pass @@ -327,8 +325,12 @@ class CalcTestCase(unittest.TestCase): return 1 def get_bsig(self): return self.bsig - def set_bsig(self, bsig): + def set_binfo(self, bsig, bkids, bkidsig, bact, bactsig): self.bsig = bsig + self.bkids = bkids + self.bkidsigs = bkidsigs + self.bact = bact + self.bactsig = bactsig def get_csig(self): return self.csig def set_csig(self, csig): @@ -414,7 +416,7 @@ class _SConsignTestCase(unittest.TestCase): path = 'not_a_valid_path' f = SCons.Sig._SConsign() - f.set_bsig('foo', 1) + f.set_binfo('foo', 1, ['f1'], ['f2'], 'foo act', 'foo actsig') assert f.get('foo') == (None, 1, None) f.set_csig('foo', 2) assert f.get('foo') == (None, 1, 2) @@ -425,7 +427,7 @@ class _SConsignTestCase(unittest.TestCase): assert f.get_implicit('foo') == ['bar'] f = SCons.Sig._SConsign(DummyModule()) - f.set_bsig('foo', 1) + f.set_binfo('foo', 1, ['f1'], ['f2'], 'foo act', 'foo actsig') assert f.get('foo') == (None, 1, None) f.set_csig('foo', 2) assert f.get('foo') == (None, 1, 2) @@ -446,20 +448,20 @@ class SConsignDBTestCase(unittest.TestCase): try: d1 = SCons.Sig.SConsignDB(DummyNode('dir1')) d1.set_timestamp('foo', 1) - d1.set_bsig('foo', 2) + d1.set_binfo('foo', 2, ['f1'], ['f2'], 'foo act', 'foo actsig') d1.set_csig('foo', 3) d1.set_timestamp('bar', 4) - d1.set_bsig('bar', 5) + d1.set_binfo('bar', 5, ['b1'], ['b2'], 'bar act', 'bar actsig') d1.set_csig('bar', 6) assert d1.get('foo') == (1, 2, 3) assert d1.get('bar') == (4, 5, 6) d2 = SCons.Sig.SConsignDB(DummyNode('dir1')) d2.set_timestamp('foo', 7) - d2.set_bsig('foo', 8) + d2.set_binfo('foo', 8, ['f3'], ['f4'], 'foo act', 'foo actsig') d2.set_csig('foo', 9) d2.set_timestamp('bar', 10) - d2.set_bsig('bar', 11) + d2.set_binfo('bar', 11, ['b3'], ['b4'], 'bar act', 'bar actsig') d2.set_csig('bar', 12) assert d2.get('foo') == (7, 8, 9) assert d2.get('bar') == (10, 11, 12) @@ -480,7 +482,7 @@ class SConsignDirFileTestCase(unittest.TestCase): path = 'not_a_valid_path' f = SCons.Sig.SConsignDirFile(DummyNode(), DummyModule()) - f.set_bsig('foo', 1) + f.set_binfo('foo', 1, ['f1'], ['f2'], 'foo act', 'foo actsig') assert f.get('foo') == (None, 1, None) f.set_csig('foo', 2) assert f.get('foo') == (None, 1, 2) diff --git a/src/engine/SCons/Sig/__init__.py b/src/engine/SCons/Sig/__init__.py index a2ebd5c..d703194 100644 --- a/src/engine/SCons/Sig/__init__.py +++ b/src/engine/SCons/Sig/__init__.py @@ -73,6 +73,10 @@ class SConsignEntry: bsig = None csig = None implicit = None + bkids = [] + bkidsigs = [] + bact = None + bactsig = None class _SConsign: """ @@ -139,9 +143,9 @@ class _SConsign: entry.csig = csig self.set_entry(filename, entry) - def set_bsig(self, filename, bsig): + def set_binfo(self, filename, bsig, bkids, bkidsigs, bact, bactsig): """ - Set the csig .sconsign entry for a file + Set the build info .sconsign entry for a file filename - the filename whose signature will be set bsig - the file's built signature @@ -149,11 +153,15 @@ class _SConsign: entry = self.get_entry(filename) entry.bsig = bsig + entry.bkids = bkids + entry.bkidsigs = bkidsigs + entry.bact = bact + entry.bactsig = bactsig self.set_entry(filename, entry) def set_timestamp(self, filename, timestamp): """ - Set the csig .sconsign entry for a file + Set the timestamp .sconsign entry for a file filename - the filename whose signature will be set timestamp - the file's timestamp @@ -171,12 +179,17 @@ class _SConsign: def set_implicit(self, filename, implicit): """Cache the implicit dependencies for 'filename'.""" entry = self.get_entry(filename) - if SCons.Util.is_String(implicit): + if not SCons.Util.is_List(implicit): implicit = [implicit] implicit = map(str, implicit) entry.implicit = implicit self.set_entry(filename, entry) + def get_binfo(self, filename): + """Fetch the cached implicit dependencies for 'filename'""" + entry = self.get_entry(filename) + return entry.bsig, entry.bkids, entry.bkidsigs, entry.bact, entry.bactsig + class SConsignDB(_SConsign): """ A _SConsign subclass that reads and writes signature information @@ -360,20 +373,28 @@ class Calculator: return bsig children = node.children() + bkids = map(str, children) - # double check bsig, because the call to childre() above may + # double check bsig, because the call to children() above may # have set it: bsig = cache.get_bsig() if bsig is not None: return bsig sigs = map(lambda n, c=self: n.calc_signature(c), children) + if node.has_builder(): - sigs.append(self.module.signature(node.get_executor())) + executor = node.get_executor() + bact = str(executor) + bactsig = self.module.signature(executor) + sigs.append(bactsig) + else: + bact = "" + bactsig = "" - bsig = self.module.collect(filter(lambda x: not x is None, sigs)) + bsig = self.module.collect(filter(None, sigs)) - cache.set_bsig(bsig) + cache.set_binfo(bsig, bkids, sigs, bact, bactsig) # don't store the bsig here, because it isn't accurate until # the node is actually built. diff --git a/test/explain.py b/test/explain.py new file mode 100644 index 0000000..d46d410 --- /dev/null +++ b/test/explain.py @@ -0,0 +1,295 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Test the --debug=explain option. +""" + +import sys +import TestSCons + +python = TestSCons.python + +test = TestSCons.TestSCons() + +test.subdir('src') + +test.write(['src', 'cat.py'], r""" +import sys + +def process(outfp, infp): + for line in infp.readlines(): + if line[:8] == 'include ': + file = line[8:-1] + try: + fp = open(file, 'rb') + except IOError: + import os + print "os.getcwd() =", os.getcwd() + raise + process(outfp, fp) + else: + outfp.write(line) + +outfp = open(sys.argv[1], 'wb') +for f in sys.argv[2:]: + if f != '-': + process(outfp, open(f, 'rb')) + +sys.exit(0) +""") + +test.write(['src', 'SConstruct'], """ +import re + +include_re = re.compile(r'^include\s+(\S+)$', re.M) + +def kfile_scan(node, env, target, arg): + contents = node.get_contents() + includes = include_re.findall(contents) + return includes + +kscan = Scanner(name = 'kfile', + function = kfile_scan, + argument = None, + skeys = ['.k']) + +cat = Builder(action = r"%s cat.py $TARGET $SOURCES") + +env = Environment() +env.Append(BUILDERS = {'Cat':cat}, + SCANNERS = kscan) + +Export("env") +SConscript('SConscript') +env.Install('../inc', 'aaa') +env.InstallAs('../inc/bbb.k', 'bbb.k') +env.Install('../inc', 'ddd') +env.InstallAs('../inc/eee', 'eee.in') +""" % (python,)) + +test.write(['src', 'SConscript'], """\ +Import("env") +env.Cat('file1', 'file1.in') +env.Cat('file2', 'file2.k') +env.Cat('file3', ['xxx', 'yyy', 'zzz']) +env.Command('file4', 'file4.in', r"%s cat.py $TARGET - $SOURCES") +env.Cat('file5', 'file5.k') +""" % (python,)) + +test.write(['src', 'aaa'], "aaa 1\n") +test.write(['src', 'bbb.k'], """\ +bbb.k 1 +include ccc +include ../inc/ddd +include ../inc/eee +""") +test.write(['src', 'ccc'], "ccc 1\n") +test.write(['src', 'ddd'], "ddd 1\n") +test.write(['src', 'eee.in'], "eee.in 1\n") + +test.write(['src', 'file1.in'], "file1.in 1\n") + +test.write(['src', 'file2.k'], """\ +file2.k 1 line 1 +include xxx +include yyy +file2.k 1 line 4 +""") + +test.write(['src', 'file4.in'], "file4.in 1\n") + +test.write(['src', 'xxx'], "xxx 1\n") +test.write(['src', 'yyy'], "yyy 1\n") +test.write(['src', 'zzz'], "zzz 1\n") + +test.write(['src', 'file5.k'], """\ +file5.k 1 line 1 +include ../inc/aaa +include ../inc/bbb.k +file5.k 1 line 4 +""") + +args = '--debug=explain .' + +# +test.run(chdir='src', arguments=args, stdout=test.wrap_stdout("""\ +scons: building `file1' because it doesn't exist +%s cat.py file1 file1.in +scons: building `file2' because it doesn't exist +%s cat.py file2 file2.k +scons: building `file3' because it doesn't exist +%s cat.py file3 xxx yyy zzz +scons: building `file4' because it doesn't exist +%s cat.py file4 - file4.in +scons: building `%s' because it doesn't exist +Install file: "aaa" as "%s" +scons: building `%s' because it doesn't exist +Install file: "ddd" as "%s" +scons: building `%s' because it doesn't exist +Install file: "eee.in" as "%s" +scons: building `%s' because it doesn't exist +Install file: "bbb.k" as "%s" +scons: building `file5' because it doesn't exist +%s cat.py file5 file5.k +""" % (python, + python, + python, + python, + test.workpath('inc', 'aaa'), + test.workpath('inc', 'aaa'), + test.workpath('inc', 'ddd'), + test.workpath('inc', 'ddd'), + test.workpath('inc', 'eee'), + test.workpath('inc', 'eee'), + test.workpath('inc', 'bbb.k'), + test.workpath('inc', 'bbb.k'), + python,))) + +test.must_match(['src', 'file1'], "file1.in 1\n") +test.must_match(['src', 'file2'], """\ +file2.k 1 line 1 +xxx 1 +yyy 1 +file2.k 1 line 4 +""") +test.must_match(['src', 'file3'], "xxx 1\nyyy 1\nzzz 1\n") +test.must_match(['src', 'file4'], "file4.in 1\n") +test.must_match(['src', 'file5'], """\ +file5.k 1 line 1 +aaa 1 +bbb.k 1 +ccc 1 +ddd 1 +eee.in 1 +file5.k 1 line 4 +""") + +# +test.write(['src', 'file1.in'], "file1.in 2\n") +test.write(['src', 'yyy'], "yyy 2\n") +test.write(['src', 'zzz'], "zzz 2\n") +test.write(['src', 'bbb.k'], "bbb.k 2\ninclude ccc\n") + +test.run(chdir='src', arguments=args, stdout=test.wrap_stdout("""\ +scons: rebuilding `file1' because `file1.in' changed +%s cat.py file1 file1.in +scons: rebuilding `file2' because `yyy' changed +%s cat.py file2 file2.k +scons: rebuilding `file3' because: + `yyy' changed + `zzz' changed +%s cat.py file3 xxx yyy zzz +scons: rebuilding `%s' because: + `%s' is no longer a dependency + `%s' is no longer a dependency + `bbb.k' changed +Install file: "bbb.k" as "%s" +scons: rebuilding `file5' because `%s' changed +%s cat.py file5 file5.k +""" % (python, + python, + python, + test.workpath('inc', 'bbb.k'), + test.workpath('inc', 'ddd'), + test.workpath('inc', 'eee'), + test.workpath('inc', 'bbb.k'), + test.workpath('inc', 'bbb.k'), + python))) + +test.must_match(['src', 'file1'], "file1.in 2\n") +test.must_match(['src', 'file2'], """\ +file2.k 1 line 1 +xxx 1 +yyy 2 +file2.k 1 line 4 +""") +test.must_match(['src', 'file3'], "xxx 1\nyyy 2\nzzz 2\n") +test.must_match(['src', 'file5'], """\ +file5.k 1 line 1 +aaa 1 +bbb.k 2 +ccc 1 +file5.k 1 line 4 +""") + +# +test.write(['src', 'SConscript'], """\ +Import("env") +env.Cat('file3', ['xxx', 'yyy']) +""") + +test.run(chdir='src', arguments=args, stdout=test.wrap_stdout("""\ +scons: rebuilding `file3' because `zzz' is no longer a dependency +%s cat.py file3 xxx yyy +""" % (python,))) + +test.must_match(['src', 'file3'], "xxx 1\nyyy 2\n") + +# +test.write(['src', 'SConscript'], """\ +Import("env") +env.Cat('file3', ['xxx', 'yyy', 'zzz']) +""") + +test.run(chdir='src', arguments=args, stdout=test.wrap_stdout("""\ +scons: rebuilding `file3' because `zzz' is a new dependency +%s cat.py file3 xxx yyy zzz +""" % (python,))) + +test.must_match(['src', 'file3'], "xxx 1\nyyy 2\nzzz 2\n") + +# +test.write(['src', 'SConscript'], """\ +Import("env") +env.Cat('file3', ['zzz', 'yyy', 'xxx']) +""") + +test.run(chdir='src', arguments=args, stdout=test.wrap_stdout("""\ +scons: rebuilding `file3' because the dependency order changed: + old: ['xxx', 'yyy', 'zzz'] + new: ['zzz', 'yyy', 'xxx'] +%s cat.py file3 zzz yyy xxx +""" % (python,))) + +test.must_match(['src', 'file3'], "zzz 2\nyyy 2\nxxx 1\n") + +# +test.write(['src', 'SConscript'], """\ +Import("env") +env.Command('file4', 'file4.in', r"%s cat.py $TARGET $SOURCES") +""" % (python,)) + +test.run(chdir='src',arguments=args, stdout=test.wrap_stdout("""\ +scons: rebuilding `file4' because the build action changed: + old: %s cat.py $TARGET - $SOURCES + new: %s cat.py $TARGET $SOURCES +%s cat.py file4 file4.in +""" % (python, python, python))) + +test.must_match(['src', 'file4'], "file4.in 1\n") + +test.pass_test() |