From d1172b37aa1d5bc66804fc7fd2c93a379cc90f0f Mon Sep 17 00:00:00 2001 From: stevenknight Date: Tue, 15 Jun 2004 12:54:45 +0000 Subject: [PATCH] Add an option to not save the --debug=explain information. git-svn-id: http://scons.tigris.org/svn/scons/trunk@993 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- doc/man/scons.1 | 16 +- src/CHANGES.txt | 5 + src/engine/SCons/Node/FS.py | 29 +- src/engine/SCons/Node/FSTests.py | 50 +++ src/engine/SCons/Node/NodeTests.py | 14 + src/engine/SCons/Node/__init__.py | 32 +- src/engine/SCons/SConsign.py | 11 +- src/engine/SCons/SConsignTests.py | 9 +- src/engine/SCons/Script/__init__.py | 13 +- test/chained-build.py | 81 ++++- test/explain.py | 538 +++++++++++++++++++++++++++- 11 files changed, 751 insertions(+), 47 deletions(-) diff --git a/doc/man/scons.1 b/doc/man/scons.1 index 51c9e4c4..63c6a83e 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -757,6 +757,18 @@ Also suppresses SCons status messages. Ignored for compatibility with GNU .BR make . +.TP +.RI --save-explain-info= 0|1 +By default, +scons will save additional information about the build +that is used by the +.B --debug=explain +option. +Setting +.B --save-explain-info=0 +will tell scons to speed up the build +by not saving this information. + .TP -t, --touch Ignored for compatibility with GNU @@ -3423,7 +3435,9 @@ which corresponds to --implicit-cache; .B max_drift which corresponds to --max-drift; .B num_jobs -which corresponds to -j and --jobs. +which corresponds to -j and --jobs; +.B save_explain_info +which corresponds to --save-explain-info. See the documentation for the corresponding command line object for information about each specific option. Example: diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 704d7b77..ee758573 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -94,6 +94,11 @@ RELEASE 0.96 - XXX - Add a --debug=explain option that reports the reason(s) why SCons thinks it must rebuild something. + - Add --save-explain-info and SetOption('save_explain_info') options + to control whether the --debug=explain information is saved in + the .sconsign file(s). Not saving this information can improve + performance and save memory usage. + - Add support for functions that return platform-independent Actions to Chmod(), Copy(), Delete(), Mkdir(), Move() and Touch() files and/or directories. Like any other Actions, the returned Action diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index 23cf8a7a..2f115c9c 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -1385,7 +1385,34 @@ class File(Base): return 0 def store_info(self, obj): - self.dir.sconsign().set_entry(self.name, obj) + # Merge our build information into the already-stored entry. + # This accomodates "chained builds" where a file that's a target + # in one build (SConstruct file) is a source in a different build. + # See test/chained-build.py for the use case. + entry = self.get_stored_info() + if not SCons.Node.Save_Explain_Info: + # If we're not saving explanation info, wipe out any that + # might be in the already-stored entry. + # + # XXX This is kind of bad that we're naming attributes that + # are really controlled in Node/__init__.py. It would be + # good to find a way to move this logic there in some way + # that still accounts for the fact that not all Node classes + # need or use this information. + attributes = [ + 'bsources', 'bsourcesigs', + 'bdepends', 'bdependsigs', + 'bimplicit', 'bimplicitsigs', + 'bact', 'bactsig', + ] + for attr in attributes: + try: + delattr(entry, attr) + except AttributeError: + pass + for key, val in obj.__dict__.items(): + entry.__dict__[key] = val + sconsign = self.dir.sconsign().set_entry(self.name, entry) def get_stored_info(self): try: diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py index d9ffd83b..d4137c1c 100644 --- a/src/engine/SCons/Node/FSTests.py +++ b/src/engine/SCons/Node/FSTests.py @@ -1969,6 +1969,55 @@ class SaveStringsTestCase(unittest.TestCase): expect = map(os.path.normpath, ['src/f', 'd1/f', 'd0/b', 'd1/b']) assert s == expect, s +class SaveExplainInfoTestCase(unittest.TestCase): + def runTest(self): + """Test how we store --debug=explain info.""" + test=TestCmd(workdir='') + fs = SCons.Node.FS.FS(test.workpath('fs')) + f = fs.File('file') + + class BInfo: + pass + + bi = BInfo() + bi.bact = 'file bact' + bi.arg1 = 'file arg1' + f.store_info(bi) + + i = f.get_stored_info() + assert i.bact == 'file bact', i.arg1 + assert i.arg1 == 'file arg1', i.arg1 + assert not hasattr(i, 'arg2'), i.bact + assert not hasattr(i, 'arg3'), i.bact + + save_value = SCons.Node.Save_Explain_Info + try: + SCons.Node.Save_Explain_Info = 1 + + bi = BInfo() + bi.arg2 = 'file arg2' + f.store_info(bi) + + i = f.get_stored_info() + assert i.bact == 'file bact', i.arg1 + assert i.arg1 == 'file arg1', i.arg1 + assert i.arg2 == 'file arg2', i.arg2 + assert not hasattr(i, 'arg3'), i.bact + + SCons.Node.Save_Explain_Info = 0 + + bi = BInfo() + bi.arg3 = 'file arg3' + f.store_info(bi) + + i = f.get_stored_info() + assert not hasattr(i, 'bact'), i.bact + assert i.arg1 == 'file arg1', i.arg1 + assert i.arg2 == 'file arg2', i.arg2 + assert i.arg3 == 'file arg3', i.arg2 + finally: + SCons.Node.Save_Explain_Info = save_value + if __name__ == "__main__": @@ -1989,5 +2038,6 @@ if __name__ == "__main__": suite.addTest(postprocessTestCase()) suite.addTest(SpecialAttrTestCase()) suite.addTest(SaveStringsTestCase()) + suite.addTest(SaveExplainInfoTestCase()) if not unittest.TextTestRunner().run(suite).wasSuccessful(): sys.exit(1) diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py index 4c7d9c22..cd7aa18a 100644 --- a/src/engine/SCons/Node/NodeTests.py +++ b/src/engine/SCons/Node/NodeTests.py @@ -396,6 +396,7 @@ class NodeTestCase(unittest.TestCase): def collect(self, args): return reduce(lambda x, y: x+y, args, self.val) self.module = M(val) + node = SCons.Node.Node() binfo = node.gen_binfo(Calculator(666)) assert isinstance(binfo, SCons.Node.BuildInfo), binfo @@ -407,6 +408,19 @@ class NodeTestCase(unittest.TestCase): assert hasattr(binfo, 'bimplicitsigs') assert binfo.bsig == 666, binfo.bsig + SCons.Node.Save_Explain_Info = 0 + + node = SCons.Node.Node() + binfo = node.gen_binfo(Calculator(777)) + assert isinstance(binfo, SCons.Node.BuildInfo), binfo + assert not hasattr(binfo, 'bsources') + assert not hasattr(binfo, 'bsourcesigs') + assert not hasattr(binfo, 'bdepends') + assert not hasattr(binfo, 'bdependsigs') + assert not hasattr(binfo, 'bimplicit') + assert not hasattr(binfo, 'bimplicitsigs') + assert binfo.bsig == 777, binfo.bsig + def test_explain(self): """Test explaining why a Node must be rebuilt """ diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index ba4cbca5..64226a62 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -76,6 +76,9 @@ implicit_deps_unchanged = 0 # controls whether the cached implicit deps are ignored: implicit_deps_changed = 0 +# controls whether --debug=explain info is saved in Nodes: +Save_Explain_Info = 1 + # A variable that can be set to an interface-specific function be called # to annotate a Node with information about its creation. def do_nothing(node): pass @@ -542,21 +545,26 @@ class Node: dependsigs = map(calc_signature, depends) implicitsigs = map(calc_signature, implicit) - binfo.bsources = map(str, sources) - binfo.bdepends = map(str, depends) - binfo.bimplicit = map(str, implicit) - - binfo.bsourcesigs = sourcesigs - binfo.bdependsigs = dependsigs - binfo.bimplicitsigs = implicitsigs - sigs = sourcesigs + dependsigs + implicitsigs - if self.has_builder(): + has_builder = self.has_builder() + if has_builder: executor = self.get_executor() - binfo.bact = str(executor) - binfo.bactsig = calc.module.signature(executor) - sigs.append(binfo.bactsig) + bactsig = calc.module.signature(executor) + sigs.append(bactsig) + + if Save_Explain_Info: + binfo.bsources = map(str, sources) + binfo.bdepends = map(str, depends) + binfo.bimplicit = map(str, implicit) + + binfo.bsourcesigs = sourcesigs + binfo.bdependsigs = dependsigs + binfo.bimplicitsigs = implicitsigs + + if has_builder: + binfo.bact = str(executor) + binfo.bactsig = calc.module.signature(executor) binfo.bsig = calc.module.collect(filter(None, sigs)) diff --git a/src/engine/SCons/SConsign.py b/src/engine/SCons/SConsign.py index c97f1b67..b7b06fe5 100644 --- a/src/engine/SCons/SConsign.py +++ b/src/engine/SCons/SConsign.py @@ -68,8 +68,7 @@ class Base: def get_entry(self, filename): """ - Create an entry for the filename and return it, or if one already exists, - then return it. + Fetch the specified entry attribute. """ return self.entries[filename] @@ -77,13 +76,7 @@ class Base: """ Set the entry. """ - try: - entry = self.entries[filename] - except KeyError: - self.entries[filename] = obj - else: - for key, val in obj.__dict__.items(): - entry.__dict__[key] = val + self.entries[filename] = obj self.dirty = 1 class DB(Base): diff --git a/src/engine/SCons/SConsignTests.py b/src/engine/SCons/SConsignTests.py index 79f43872..16ef8164 100644 --- a/src/engine/SCons/SConsignTests.py +++ b/src/engine/SCons/SConsignTests.py @@ -47,8 +47,9 @@ class BaseTestCase(unittest.TestCase): aaa = BuildInfo('aaa') bbb = BuildInfo('bbb') + bbb.arg1 = 'bbb arg1' ccc = BuildInfo('ccc') - ccc.arg = 'ccc arg' + ccc.arg2 = 'ccc arg2' f = SCons.SConsign.Base() f.set_entry('aaa', aaa) @@ -61,12 +62,14 @@ class BaseTestCase(unittest.TestCase): e = f.get_entry('bbb') assert e == bbb, e assert e.name == 'bbb', e.name - assert not hasattr(e, 'arg'), e + assert e.arg1 == 'bbb arg1', e.arg1 + assert not hasattr(e, 'arg2'), e f.set_entry('bbb', ccc) e = f.get_entry('bbb') assert e.name == 'ccc', e.name - assert e.arg == 'ccc arg', e.arg + assert not hasattr(e, 'arg1'), e + assert e.arg2 == 'ccc arg2', e.arg1 ddd = BuildInfo('ddd') eee = BuildInfo('eee') diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py index d4650d20..835409eb 100644 --- a/src/engine/SCons/Script/__init__.py +++ b/src/engine/SCons/Script/__init__.py @@ -528,7 +528,6 @@ class OptParser(OptionParser): def opt_debug(option, opt, value, parser, debug_options=debug_options): if value in debug_options: - #setattr(parser.values, 'debug', value) parser.values.debug = value else: raise OptionValueError("Warning: %s is not a valid debug type" % value) @@ -541,7 +540,6 @@ class OptParser(OptionParser): def opt_duplicate(option, opt, value, parser): if not value in SCons.Node.FS.Valid_Duplicates: raise OptionValueError("`%s' is not a valid duplication style." % value) - #setattr(parser.values, 'duplicate', value) parser.values.duplicate = value # Set the duplicate style right away so it can affect linking # of SConscript files. @@ -584,7 +582,6 @@ class OptParser(OptionParser): def opt_j(option, opt, value, parser): value = int(value) - #setattr(parser.values, 'num_jobs', value) parser.values.num_jobs = value self.add_option('-j', '--jobs', action="callback", type="int", callback=opt_j, metavar="N", @@ -627,6 +624,10 @@ class OptParser(OptionParser): self.add_option('-s', '--silent', '--quiet', action="store_true", default=0, help="Don't print commands.") + self.add_option('--save-explain-info', type="int", action="store", + dest='save_explain_info', metavar='0|1', + help="(Don't) save --debug=explain information") + self.add_option('-u', '--up', '--search-up', action="store_const", dest="climb_up", default=0, const=1, help="Search up directory tree for SConstruct, " @@ -734,7 +735,8 @@ class SConscriptSettableOptions: 'max_drift':SCons.Sig.default_max_drift, 'implicit_cache':0, 'clean':0, - 'duplicate':'hard-soft-copy'} + 'duplicate':'hard-soft-copy', + 'save_explain_info':1} def get(self, name): if not self.settable.has_key(name): @@ -770,7 +772,7 @@ class SConscriptSettableOptions: # Set the duplicate stye right away so it can affect linking # of SConscript files. SCons.Node.FS.set_duplicate(value) - + self.settable[name] = value @@ -947,6 +949,7 @@ def _main(args, parser): # that are SConscript settable: SCons.Node.implicit_cache = ssoptions.get('implicit_cache') SCons.Node.FS.set_duplicate(ssoptions.get('duplicate')) + SCons.Node.Save_Explain_Info = ssoptions.get('save_explain_info') or print_explanations lookup_top = None if targets: diff --git a/test/chained-build.py b/test/chained-build.py index e4463e5a..ff14964c 100644 --- a/test/chained-build.py +++ b/test/chained-build.py @@ -28,40 +28,93 @@ import TestSCons test = TestSCons.TestSCons() -test.write('SConstruct1', """ +test.subdir('w1', 'w2') + +SConstruct1_contents = """\ def build(env, target, source): open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read()) env=Environment(BUILDERS={'B' : Builder(action=build)}) env.B('foo.mid', 'foo.in') -""") +""" -test.write('SConstruct2', """ +SConstruct2_contents = """\ def build(env, target, source): open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read()) env=Environment(BUILDERS={'B' : Builder(action=build)}) env.B('foo.out', 'foo.mid') -""") +""" + +# Test with the default of saving explanation info. +test.write(['w1', 'SConstruct1'], SConstruct1_contents) +test.write(['w1', 'SConstruct2'], SConstruct2_contents) +test.write(['w1', 'foo.in'], "foo.in 1") + +test.run(chdir='w1', + arguments="--max-drift=0 -f SConstruct1 foo.mid", + stdout = test.wrap_stdout('build("foo.mid", "foo.in")\n')) +test.run(chdir='w1', + arguments="--max-drift=0 -f SConstruct2 foo.out", + stdout = test.wrap_stdout('build("foo.out", "foo.mid")\n')) + +test.up_to_date(chdir='w1', + options="--max-drift=0 -f SConstruct1", + arguments="foo.mid") +test.up_to_date(chdir='w1', + options="--max-drift=0 -f SConstruct2", + arguments="foo.out") + +test.write(['w1', 'foo.in'], "foo.in 2") + +test.run(chdir='w1', + arguments="--max-drift=0 -f SConstruct1 foo.mid", + stdout = test.wrap_stdout('build("foo.mid", "foo.in")\n')) +test.run(chdir='w1', + arguments="--max-drift=0 -f SConstruct2 foo.out", + stdout = test.wrap_stdout('build("foo.out", "foo.mid")\n')) + +test.up_to_date(chdir='w1', + options="--max-drift=0 -f SConstruct1", + arguments="foo.mid") +test.up_to_date(chdir='w1', + options="--max-drift=0 -f SConstruct2", + arguments="foo.out") -test.write('foo.in', "foo.in") +# Now test when we're not saving explanation info. +preamble = "SetOption('save_explain_info', 0)\n" +test.write(['w2', 'SConstruct1'], preamble + SConstruct1_contents) +test.write(['w2', 'SConstruct2'], preamble + SConstruct2_contents) +test.write(['w2', 'foo.in'], "foo.in 1") -test.run(arguments="--max-drift=0 -f SConstruct1 foo.mid", +test.run(chdir='w2', + arguments="--max-drift=0 -f SConstruct1 foo.mid", stdout = test.wrap_stdout('build("foo.mid", "foo.in")\n')) -test.run(arguments="--max-drift=0 -f SConstruct2 foo.out", +test.run(chdir='w2', + arguments="--max-drift=0 -f SConstruct2 foo.out", stdout = test.wrap_stdout('build("foo.out", "foo.mid")\n')) -test.up_to_date(options="--max-drift=0 -f SConstruct1", arguments="foo.mid") -test.up_to_date(options="--max-drift=0 -f SConstruct2", arguments="foo.out") +test.up_to_date(chdir='w2', + options="--max-drift=0 -f SConstruct1", + arguments="foo.mid") +test.up_to_date(chdir='w2', + options="--max-drift=0 -f SConstruct2", + arguments="foo.out") -test.write('foo.in', "foo.in 2") +test.write(['w2', 'foo.in'], "foo.in 2") -test.run(arguments="--max-drift=0 -f SConstruct1 foo.mid", +test.run(chdir='w2', + arguments="--max-drift=0 -f SConstruct1 foo.mid", stdout = test.wrap_stdout('build("foo.mid", "foo.in")\n')) -test.run(arguments="--max-drift=0 -f SConstruct2 foo.out", +test.run(chdir='w2', + arguments="--max-drift=0 -f SConstruct2 foo.out", stdout = test.wrap_stdout('build("foo.out", "foo.mid")\n')) -test.up_to_date(options="--max-drift=0 -f SConstruct1", arguments="foo.mid") -test.up_to_date(options="--max-drift=0 -f SConstruct2", arguments="foo.out") +test.up_to_date(chdir='w2', + options="--max-drift=0 -f SConstruct1", + arguments="foo.mid") +test.up_to_date(chdir='w2', + options="--max-drift=0 -f SConstruct2", + arguments="foo.out") test.pass_test() diff --git a/test/explain.py b/test/explain.py index e3f80e42..4526ca94 100644 --- a/test/explain.py +++ b/test/explain.py @@ -38,7 +38,9 @@ python = TestSCons.python test = TestSCons.TestSCons() test.subdir('work1', ['work1', 'src'], ['work1', 'src', 'subdir'], - 'work2', ['work2', 'src'], ['work2', 'src', 'subdir']) + 'work2', ['work2', 'src'], ['work2', 'src', 'subdir'], + 'work3', ['work3', 'src'], ['work3', 'src', 'subdir'], + 'work4', ['work4', 'src'], ['work4', 'src', 'subdir']) subdir_file6 = os.path.join('subdir', 'file6') subdir_file6_in = os.path.join('subdir', 'file6.in') @@ -100,7 +102,7 @@ env.InstallAs('../inc/eee', 'eee.in') args = '--debug=explain .' -# +############################################################################# test.write(['work1', 'src', 'SConstruct'], SConstruct_contents) test.write(['work1', 'src', 'SConscript'], """\ @@ -323,4 +325,536 @@ test.run(chdir='work1/src', arguments=args) test.up_to_date(chdir='work1/src',arguments='.') +############################################################################# +# Now test (in a separate workspace) how things function when +# we tell SCons to not save the --debug=explain info +# using SetOption('save_explain_info'). +test.write(['work2', 'src', 'SConstruct'], + "SetOption('save_explain_info', 0)\n" + SConstruct_contents) + +test.write(['work2', '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 %s $TARGET - $SOURCES") +env.Cat('file5', 'file5.k') +env.Cat('subdir/file6', 'subdir/file6.in') +""" % (python, cat_py)) + +test.write(['work2', 'src', 'aaa'], "aaa 1\n") +test.write(['work2', 'src', 'bbb.k'], """\ +bbb.k 1 +include ccc +include ../inc/ddd +include ../inc/eee +""") +test.write(['work2', 'src', 'ccc'], "ccc 1\n") +test.write(['work2', 'src', 'ddd'], "ddd 1\n") +test.write(['work2', 'src', 'eee.in'], "eee.in 1\n") + +test.write(['work2', 'src', 'file1.in'], "file1.in 1\n") + +test.write(['work2', 'src', 'file2.k'], """\ +file2.k 1 line 1 +include xxx +include yyy +file2.k 1 line 4 +""") + +test.write(['work2', 'src', 'file4.in'], "file4.in 1\n") + +test.write(['work2', 'src', 'xxx'], "xxx 1\n") +test.write(['work2', 'src', 'yyy'], "yyy 1\n") +test.write(['work2', 'src', 'zzz'], "zzz 1\n") + +test.write(['work2', 'src', 'file5.k'], """\ +file5.k 1 line 1 +include ../inc/aaa +include ../inc/bbb.k +file5.k 1 line 4 +""") + +test.write(['work2', 'src', 'subdir', 'file6.in'], "subdir/file6.in 1\n") + +# First, even without build info, we can tell the user that things +# are being built because they don't exist. +test.run(chdir='work2/src', arguments=args, stdout=test.wrap_stdout("""\ +scons: building `file1' because it doesn't exist +%s %s file1 file1.in +scons: building `file2' because it doesn't exist +%s %s file2 file2.k +scons: building `file3' because it doesn't exist +%s %s file3 xxx yyy zzz +scons: building `file4' because it doesn't exist +%s %s 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 %s file5 file5.k +scons: building `%s' because it doesn't exist +%s %s %s %s +""" % (python, cat_py, + python, cat_py, + python, cat_py, + python, cat_py, + test.workpath('work2', 'inc', 'aaa'), + test.workpath('work2', 'inc', 'aaa'), + test.workpath('work2', 'inc', 'ddd'), + test.workpath('work2', 'inc', 'ddd'), + test.workpath('work2', 'inc', 'eee'), + test.workpath('work2', 'inc', 'eee'), + test.workpath('work2', 'inc', 'bbb.k'), + test.workpath('work2', 'inc', 'bbb.k'), + python, cat_py, + subdir_file6, + python, cat_py, subdir_file6, subdir_file6_in))) + +test.must_match(['work2', 'src', 'file1'], "file1.in 1\n") +test.must_match(['work2', 'src', 'file2'], """\ +file2.k 1 line 1 +xxx 1 +yyy 1 +file2.k 1 line 4 +""") +test.must_match(['work2', 'src', 'file3'], "xxx 1\nyyy 1\nzzz 1\n") +test.must_match(['work2', 'src', 'file4'], "file4.in 1\n") +test.must_match(['work2', 'src', 'file5'], """\ +file5.k 1 line 1 +aaa 1 +bbb.k 1 +ccc 1 +ddd 1 +eee.in 1 +file5.k 1 line 4 +""") + +# Using --debug=explain above will have actually saved the build info; +# run again to clear it out. +test.write(['work2', 'src', 'file1.in'], "file1.in 2\n") +test.write(['work2', 'src', 'yyy'], "yyy 2\n") +test.write(['work2', 'src', 'zzz'], "zzz 2\n") +test.write(['work2', 'src', 'bbb.k'], "bbb.k 2\ninclude ccc\n") + +test.run(chdir='work2/src', arguments='.') + +# Now, it should tell us that it can't explain why the files are +# being rebuilt. It should also *store* the build info because +# we're using --debug=explain... +test.write(['work2', 'src', 'file1.in'], "file1.in 3\n") +test.write(['work2', 'src', 'yyy'], "yyy 3\n") +test.write(['work2', 'src', 'zzz'], "zzz 3\n") +test.write(['work2', 'src', 'bbb.k'], "bbb.k 3\ninclude ccc\n") + +test.run(chdir='work2/src', arguments=args, stdout=test.wrap_stdout("""\ +scons: Cannot explain why `file1' is being rebuilt: No previous build information found +%s %s file1 file1.in +scons: Cannot explain why `file2' is being rebuilt: No previous build information found +%s %s file2 file2.k +scons: Cannot explain why `file3' is being rebuilt: No previous build information found +%s %s file3 xxx yyy zzz +scons: Cannot explain why `%s' is being rebuilt: No previous build information found +Install file: "bbb.k" as "%s" +scons: Cannot explain why `file5' is being rebuilt: No previous build information found +%s %s file5 file5.k +""" % (python, cat_py, + python, cat_py, + python, cat_py, + test.workpath('work2', 'inc', 'bbb.k'), + test.workpath('work2', 'inc', 'bbb.k'), + python, cat_py))) + +test.must_match(['work2', 'src', 'file1'], "file1.in 3\n") +test.must_match(['work2', 'src', 'file2'], """\ +file2.k 1 line 1 +xxx 1 +yyy 3 +file2.k 1 line 4 +""") +test.must_match(['work2', 'src', 'file3'], "xxx 1\nyyy 3\nzzz 3\n") +test.must_match(['work2', 'src', 'file5'], """\ +file5.k 1 line 1 +aaa 1 +bbb.k 3 +ccc 1 +file5.k 1 line 4 +""") + +# ...so if we now update the files again, it should be able to tell +# us why the files changed. +test.write(['work2', 'src', 'file1.in'], "file1.in 4\n") +test.write(['work2', 'src', 'yyy'], "yyy 4\n") +test.write(['work2', 'src', 'zzz'], "zzz 4\n") +test.write(['work2', 'src', 'bbb.k'], "bbb.k 4\ninclude ccc\n") + +test.run(chdir='work2/src', arguments=args, stdout=test.wrap_stdout("""\ +scons: rebuilding `file1' because `file1.in' changed +%s %s file1 file1.in +scons: rebuilding `file2' because `yyy' changed +%s %s file2 file2.k +scons: rebuilding `file3' because: + `yyy' changed + `zzz' changed +%s %s file3 xxx yyy zzz +scons: rebuilding `%s' because `bbb.k' changed +Install file: "bbb.k" as "%s" +scons: rebuilding `file5' because `%s' changed +%s %s file5 file5.k +""" % (python, cat_py, + python, cat_py, + python, cat_py, + test.workpath('work2', 'inc', 'bbb.k'), + test.workpath('work2', 'inc', 'bbb.k'), + test.workpath('work2', 'inc', 'bbb.k'), + python, cat_py))) + +test.must_match(['work2', 'src', 'file1'], "file1.in 4\n") +test.must_match(['work2', 'src', 'file2'], """\ +file2.k 1 line 1 +xxx 1 +yyy 4 +file2.k 1 line 4 +""") +test.must_match(['work2', 'src', 'file3'], "xxx 1\nyyy 4\nzzz 4\n") +test.must_match(['work2', 'src', 'file5'], """\ +file5.k 1 line 1 +aaa 1 +bbb.k 4 +ccc 1 +file5.k 1 line 4 +""") + +############################################################################# +# Now test (in a separate workspace) how things function when +# we tell SCons to not save the --debug=explain info +# using --save-explain-info=0'. +test.write(['work3', 'src', 'SConstruct'], SConstruct_contents) + +test.write(['work3', '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 %s $TARGET - $SOURCES") +env.Cat('file5', 'file5.k') +env.Cat('subdir/file6', 'subdir/file6.in') +""" % (python, cat_py)) + +test.write(['work3', 'src', 'aaa'], "aaa 1\n") +test.write(['work3', 'src', 'bbb.k'], """\ +bbb.k 1 +include ccc +include ../inc/ddd +include ../inc/eee +""") +test.write(['work3', 'src', 'ccc'], "ccc 1\n") +test.write(['work3', 'src', 'ddd'], "ddd 1\n") +test.write(['work3', 'src', 'eee.in'], "eee.in 1\n") + +test.write(['work3', 'src', 'file1.in'], "file1.in 1\n") + +test.write(['work3', 'src', 'file2.k'], """\ +file2.k 1 line 1 +include xxx +include yyy +file2.k 1 line 4 +""") + +test.write(['work3', 'src', 'file4.in'], "file4.in 1\n") + +test.write(['work3', 'src', 'xxx'], "xxx 1\n") +test.write(['work3', 'src', 'yyy'], "yyy 1\n") +test.write(['work3', 'src', 'zzz'], "zzz 1\n") + +test.write(['work3', 'src', 'file5.k'], """\ +file5.k 1 line 1 +include ../inc/aaa +include ../inc/bbb.k +file5.k 1 line 4 +""") + +test.write(['work3', 'src', 'subdir', 'file6.in'], "subdir/file6.in 1\n") + +# First, even without build info and regardless of storage option, +# we can tell the user that things are being built because they don't exist. +test.run(chdir='work3/src', + arguments='--debug=explain --save-explain-info=0 .', + stdout=test.wrap_stdout("""\ +scons: building `file1' because it doesn't exist +%s %s file1 file1.in +scons: building `file2' because it doesn't exist +%s %s file2 file2.k +scons: building `file3' because it doesn't exist +%s %s file3 xxx yyy zzz +scons: building `file4' because it doesn't exist +%s %s 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 %s file5 file5.k +scons: building `%s' because it doesn't exist +%s %s %s %s +""" % (python, cat_py, + python, cat_py, + python, cat_py, + python, cat_py, + test.workpath('work3', 'inc', 'aaa'), + test.workpath('work3', 'inc', 'aaa'), + test.workpath('work3', 'inc', 'ddd'), + test.workpath('work3', 'inc', 'ddd'), + test.workpath('work3', 'inc', 'eee'), + test.workpath('work3', 'inc', 'eee'), + test.workpath('work3', 'inc', 'bbb.k'), + test.workpath('work3', 'inc', 'bbb.k'), + python, cat_py, + subdir_file6, + python, cat_py, subdir_file6, subdir_file6_in))) + +test.must_match(['work3', 'src', 'file1'], "file1.in 1\n") +test.must_match(['work3', 'src', 'file2'], """\ +file2.k 1 line 1 +xxx 1 +yyy 1 +file2.k 1 line 4 +""") +test.must_match(['work3', 'src', 'file3'], "xxx 1\nyyy 1\nzzz 1\n") +test.must_match(['work3', 'src', 'file4'], "file4.in 1\n") +test.must_match(['work3', 'src', 'file5'], """\ +file5.k 1 line 1 +aaa 1 +bbb.k 1 +ccc 1 +ddd 1 +eee.in 1 +file5.k 1 line 4 +""") + +# Using --debug=explain above will have actually saved the build info; +# run again to clear it out. +test.write(['work3', 'src', 'file1.in'], "file1.in 2\n") +test.write(['work3', 'src', 'yyy'], "yyy 2\n") +test.write(['work3', 'src', 'zzz'], "zzz 2\n") +test.write(['work3', 'src', 'bbb.k'], "bbb.k 2\ninclude ccc\n") + +test.run(chdir='work3/src', arguments='--save-explain-info=0 .') + +# Now, it should tell us that it can't explain why the files are +# being rebuilt. It should also *store* the build info because +# we're using --debug=explain... +test.write(['work3', 'src', 'file1.in'], "file1.in 3\n") +test.write(['work3', 'src', 'yyy'], "yyy 3\n") +test.write(['work3', 'src', 'zzz'], "zzz 3\n") +test.write(['work3', 'src', 'bbb.k'], "bbb.k 3\ninclude ccc\n") + +test.run(chdir='work3/src', + arguments='--debug=explain .', + stdout=test.wrap_stdout("""\ +scons: Cannot explain why `file1' is being rebuilt: No previous build information found +%s %s file1 file1.in +scons: Cannot explain why `file2' is being rebuilt: No previous build information found +%s %s file2 file2.k +scons: Cannot explain why `file3' is being rebuilt: No previous build information found +%s %s file3 xxx yyy zzz +scons: Cannot explain why `%s' is being rebuilt: No previous build information found +Install file: "bbb.k" as "%s" +scons: Cannot explain why `file5' is being rebuilt: No previous build information found +%s %s file5 file5.k +""" % (python, cat_py, + python, cat_py, + python, cat_py, + test.workpath('work3', 'inc', 'bbb.k'), + test.workpath('work3', 'inc', 'bbb.k'), + python, cat_py))) + +test.must_match(['work3', 'src', 'file1'], "file1.in 3\n") +test.must_match(['work3', 'src', 'file2'], """\ +file2.k 1 line 1 +xxx 1 +yyy 3 +file2.k 1 line 4 +""") +test.must_match(['work3', 'src', 'file3'], "xxx 1\nyyy 3\nzzz 3\n") +test.must_match(['work3', 'src', 'file5'], """\ +file5.k 1 line 1 +aaa 1 +bbb.k 3 +ccc 1 +file5.k 1 line 4 +""") + +# ...so if we now update the files again, it should be able to tell +# us why the files changed. +test.write(['work3', 'src', 'file1.in'], "file1.in 4\n") +test.write(['work3', 'src', 'yyy'], "yyy 4\n") +test.write(['work3', 'src', 'zzz'], "zzz 4\n") +test.write(['work3', 'src', 'bbb.k'], "bbb.k 4\ninclude ccc\n") + +test.run(chdir='work3/src', arguments=args, stdout=test.wrap_stdout("""\ +scons: rebuilding `file1' because `file1.in' changed +%s %s file1 file1.in +scons: rebuilding `file2' because `yyy' changed +%s %s file2 file2.k +scons: rebuilding `file3' because: + `yyy' changed + `zzz' changed +%s %s file3 xxx yyy zzz +scons: rebuilding `%s' because `bbb.k' changed +Install file: "bbb.k" as "%s" +scons: rebuilding `file5' because `%s' changed +%s %s file5 file5.k +""" % (python, cat_py, + python, cat_py, + python, cat_py, + test.workpath('work3', 'inc', 'bbb.k'), + test.workpath('work3', 'inc', 'bbb.k'), + test.workpath('work3', 'inc', 'bbb.k'), + python, cat_py))) + +test.must_match(['work3', 'src', 'file1'], "file1.in 4\n") +test.must_match(['work3', 'src', 'file2'], """\ +file2.k 1 line 1 +xxx 1 +yyy 4 +file2.k 1 line 4 +""") +test.must_match(['work3', 'src', 'file3'], "xxx 1\nyyy 4\nzzz 4\n") +test.must_match(['work3', 'src', 'file5'], """\ +file5.k 1 line 1 +aaa 1 +bbb.k 4 +ccc 1 +file5.k 1 line 4 +""") + +############################################################################# +# Test that the --debug=explain information gets saved by default. +test.write(['work4', 'src', 'SConstruct'], SConstruct_contents) + +test.write(['work4', '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 %s $TARGET - $SOURCES") +env.Cat('file5', 'file5.k') +env.Cat('subdir/file6', 'subdir/file6.in') +""" % (python, cat_py)) + +test.write(['work4', 'src', 'aaa'], "aaa 1\n") +test.write(['work4', 'src', 'bbb.k'], """\ +bbb.k 1 +include ccc +include ../inc/ddd +include ../inc/eee +""") +test.write(['work4', 'src', 'ccc'], "ccc 1\n") +test.write(['work4', 'src', 'ddd'], "ddd 1\n") +test.write(['work4', 'src', 'eee.in'], "eee.in 1\n") + +test.write(['work4', 'src', 'file1.in'], "file1.in 1\n") + +test.write(['work4', 'src', 'file2.k'], """\ +file2.k 1 line 1 +include xxx +include yyy +file2.k 1 line 4 +""") + +test.write(['work4', 'src', 'file4.in'], "file4.in 1\n") + +test.write(['work4', 'src', 'xxx'], "xxx 1\n") +test.write(['work4', 'src', 'yyy'], "yyy 1\n") +test.write(['work4', 'src', 'zzz'], "zzz 1\n") + +test.write(['work4', 'src', 'file5.k'], """\ +file5.k 1 line 1 +include ../inc/aaa +include ../inc/bbb.k +file5.k 1 line 4 +""") + +test.write(['work4', 'src', 'subdir', 'file6.in'], "subdir/file6.in 1\n") + +# +test.run(chdir='work4/src', arguments='.') + +test.must_match(['work4', 'src', 'file1'], "file1.in 1\n") +test.must_match(['work4', 'src', 'file2'], """\ +file2.k 1 line 1 +xxx 1 +yyy 1 +file2.k 1 line 4 +""") +test.must_match(['work4', 'src', 'file3'], "xxx 1\nyyy 1\nzzz 1\n") +test.must_match(['work4', 'src', 'file4'], "file4.in 1\n") +test.must_match(['work4', '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(['work4', 'src', 'file1.in'], "file1.in 2\n") +test.write(['work4', 'src', 'yyy'], "yyy 2\n") +test.write(['work4', 'src', 'zzz'], "zzz 2\n") +test.write(['work4', 'src', 'bbb.k'], "bbb.k 2\ninclude ccc\n") + +test.run(chdir='work4/src', arguments=args, stdout=test.wrap_stdout("""\ +scons: rebuilding `file1' because `file1.in' changed +%s %s file1 file1.in +scons: rebuilding `file2' because `yyy' changed +%s %s file2 file2.k +scons: rebuilding `file3' because: + `yyy' changed + `zzz' changed +%s %s 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 %s file5 file5.k +""" % (python, cat_py, + python, cat_py, + python, cat_py, + test.workpath('work4', 'inc', 'bbb.k'), + test.workpath('work4', 'inc', 'ddd'), + test.workpath('work4', 'inc', 'eee'), + test.workpath('work4', 'inc', 'bbb.k'), + test.workpath('work4', 'inc', 'bbb.k'), + python, cat_py))) + +test.must_match(['work4', 'src', 'file1'], "file1.in 2\n") +test.must_match(['work4', 'src', 'file2'], """\ +file2.k 1 line 1 +xxx 1 +yyy 2 +file2.k 1 line 4 +""") +test.must_match(['work4', 'src', 'file3'], "xxx 1\nyyy 2\nzzz 2\n") +test.must_match(['work4', 'src', 'file5'], """\ +file5.k 1 line 1 +aaa 1 +bbb.k 2 +ccc 1 +file5.k 1 line 4 +""") + test.pass_test() -- 2.26.2