summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2004-06-15 12:54:45 (GMT)
committerSteven Knight <knight@baldmt.com>2004-06-15 12:54:45 (GMT)
commit12c00a335246a2609b33d3a57a8fe59a50ce6161 (patch)
tree96cf9c87582bafacbe731704f4f10fbd8e34d331
parent521fade05a01a1a5ce5e1c8b4e6fbd3a60da5d18 (diff)
downloadSCons-12c00a335246a2609b33d3a57a8fe59a50ce6161.zip
SCons-12c00a335246a2609b33d3a57a8fe59a50ce6161.tar.gz
SCons-12c00a335246a2609b33d3a57a8fe59a50ce6161.tar.bz2
Add an option to not save the --debug=explain information.
-rw-r--r--doc/man/scons.116
-rw-r--r--src/CHANGES.txt5
-rw-r--r--src/engine/SCons/Node/FS.py29
-rw-r--r--src/engine/SCons/Node/FSTests.py50
-rw-r--r--src/engine/SCons/Node/NodeTests.py14
-rw-r--r--src/engine/SCons/Node/__init__.py32
-rw-r--r--src/engine/SCons/SConsign.py11
-rw-r--r--src/engine/SCons/SConsignTests.py9
-rw-r--r--src/engine/SCons/Script/__init__.py13
-rw-r--r--test/chained-build.py81
-rw-r--r--test/explain.py538
11 files changed, 751 insertions, 47 deletions
diff --git a/doc/man/scons.1 b/doc/man/scons.1
index 51c9e4c..63c6a83 100644
--- a/doc/man/scons.1
+++ b/doc/man/scons.1
@@ -758,6 +758,18 @@ 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
.BR make .
@@ -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 704d7b7..ee75857 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 23cf8a7..2f115c9 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 d9ffd83..d4137c1 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 4c7d9c2..cd7aa18 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 ba4cbca..64226a6 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 c97f1b6..b7b06fe 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 79f4387..16ef816 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 d4650d2..835409e 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 e4463e5..ff14964 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 e3f80e4..4526ca9 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()