- Add support for the strfunction argument to all types of Actions:
CommandAction, ListAction, and CommandGeneratorAction.
+ - Speed up turning file system Nodes into strings by caching the
+ values after we're finished reading the SConscript files.
+
From Gary Oberbrunner:
- Add a --debug=presub option to print actions prior to substitution.
src_suffixes = self.src_suffixes(env)
for snode in slist:
- base, ext = self.splitext(str(snode))
- if sdict.has_key(ext):
- tgt = sdict[ext]._execute(env, None, snode, overwarn)
+ try:
+ get_suffix = snode.get_suffix
+ except AttributeError:
+ ext = self.splitext(str(snode))
+ else:
+ ext = get_suffix()
+ try:
+ subsidiary_builder = sdict[ext]
+ except KeyError:
+ final_sources.append(snode)
+ else:
+ tgt = subsidiary_builder._execute(env, None, snode, overwarn)
# Only supply the builder with sources it is capable
# of building.
if SCons.Util.is_List(tgt):
final_sources.append(tgt)
else:
final_sources.extend(tgt)
- else:
- final_sources.append(snode)
return BuilderBase._execute(self, env, target, final_sources, overwarn)
import SCons.Util
import SCons.Warnings
+#
+# We stringify these file system Nodes a lot. Turning a file system Node
+# into a string is non-trivial, because the final string representation
+# can depend on a lot of factors: whether it's a derived target or not,
+# whether it's linked to a repository or source directory, and whether
+# there's duplication going on. The normal technique for optimizing
+# calculations like this is to memoize (cache) the string value, so you
+# only have to do the calculation once.
+#
+# A number of the above factors, however, can be set after we've already
+# been asked to return a string for a Node, because a Repository() or
+# BuildDir() call or the like may not occur until later in SConscript
+# files. So this variable controls whether we bother trying to save
+# string values for Nodes. The wrapper interface can set this whenever
+# they're done mucking with Repository and BuildDir and the other stuff,
+# to let this module know it can start returning saved string values
+# for Nodes.
+#
+Save_Strings = None
+
+def save_strings(val):
+ global Save_Strings
+ Save_Strings = val
+
#
# SCons.Action objects for interacting with the outside world.
#
delattr(self, '_rexists')
except AttributeError:
pass
+ try:
+ delattr(self, '_str_val')
+ except AttributeError:
+ pass
+ self.relpath = {}
def get_dir(self):
return self.dir
+ def get_suffix(self):
+ return SCons.Util.splitext(self.name)[1]
+
def __str__(self):
"""A Node.FS.Base object's string representation is its path
name."""
- if self.duplicate or self.is_derived():
- return self.get_path()
- return self.srcnode().get_path()
+ try:
+ return self._str_val
+ except AttributeError:
+ global Save_Strings
+ if self.duplicate or self.is_derived():
+ str_val = self.get_path()
+ else:
+ str_val = self.srcnode().get_path()
+ if Save_Strings:
+ self._str_val = str_val
+ return str_val
def exists(self):
try:
return node.get_found_includes(env, scanner, target)
def scanner_key(self):
- return SCons.Util.splitext(self.name)[1]
+ return self.get_suffix()
def get_contents(self):
"""Fetch the contents of the entry.
del node._srcnode
except AttributeError:
pass
+ try:
+ del node._str_val
+ except AttributeError:
+ pass
if duplicate != None:
node.duplicate=duplicate
return self.dir.root()
def scanner_key(self):
- return SCons.Util.splitext(self.name)[1]
+ return self.get_suffix()
def get_contents(self):
if not self.rexists():
e = fs.Entry('e')
e._exists = 1
e._rexists = 1
+ e._str_val = 'e'
e.clear()
assert not hasattr(e, '_exists')
assert not hasattr(e, '_rexists')
+ assert not hasattr(e, '_str_val')
d = fs.Dir('d')
d._exists = 1
d._rexists = 1
+ d._str_val = 'd'
d.clear()
assert not hasattr(d, '_exists')
assert not hasattr(d, '_rexists')
+ assert not hasattr(d, '_str_val')
f = fs.File('f')
f._exists = 1
f._rexists = 1
+ f._str_val = 'f'
f.clear()
assert not hasattr(f, '_exists')
assert not hasattr(f, '_rexists')
+ assert not hasattr(f, '_str_val')
class postprocessTestCase(unittest.TestCase):
def runTest(self):
caught = 1
assert caught, "did not catch expected AttributeError"
+class SaveStringsTestCase(unittest.TestCase):
+ def runTest(self):
+ """Test caching string values of nodes."""
+ test=TestCmd(workdir='')
+
+ def setup(fs):
+ fs.Dir('src')
+ fs.Dir('d0')
+ fs.Dir('d1')
+
+ d0_f = fs.File('d0/f')
+ d1_f = fs.File('d1/f')
+ d0_b = fs.File('d0/b')
+ d1_b = fs.File('d1/b')
+ d1_f.duplicate = 1
+ d1_b.duplicate = 1
+ d0_b.builder = 1
+ d1_b.builder = 1
+
+ return [d0_f, d1_f, d0_b, d1_b]
+
+ def modify(nodes):
+ d0_f, d1_f, d0_b, d1_b = nodes
+ d1_f.duplicate = 0
+ d1_b.duplicate = 0
+ d0_b.builder = 0
+ d1_b.builder = 0
+
+ fs1 = SCons.Node.FS.FS(test.workpath('fs1'))
+ nodes = setup(fs1)
+ fs1.BuildDir('d0', 'src', duplicate=0)
+ fs1.BuildDir('d1', 'src', duplicate=1)
+
+ s = map(str, nodes)
+ assert s == ['src/f', 'd1/f', 'd0/b', 'd1/b'], s
+
+ modify(nodes)
+
+ s = map(str, nodes)
+ assert s == ['src/f', 'src/f', 'd0/b', 'd1/b'], s
+
+ SCons.Node.FS.save_strings(1)
+ fs2 = SCons.Node.FS.FS(test.workpath('fs2'))
+ nodes = setup(fs2)
+ fs2.BuildDir('d0', 'src', duplicate=0)
+ fs2.BuildDir('d1', 'src', duplicate=1)
+
+ s = map(str, nodes)
+ assert s == ['src/f', 'd1/f', 'd0/b', 'd1/b'], s
+
+ modify(nodes)
+
+ s = map(str, nodes)
+ assert s == ['src/f', 'd1/f', 'd0/b', 'd1/b'], s
+
if __name__ == "__main__":
suite.addTest(clearTestCase())
suite.addTest(postprocessTestCase())
suite.addTest(SpecialAttrTestCase())
+ suite.addTest(SaveStringsTestCase())
if not unittest.TextTestRunner().run(suite).wasSuccessful():
sys.exit(1)
siginfo = n.get_prevsiginfo()
assert siginfo == (None, None, None), siginfo
+ def test_get_suffix(self):
+ """Test the base Node get_suffix() method"""
+ n = SCons.Node.Node()
+ s = n.get_suffix()
+ assert s == '', s
+
def test_generate_build_dict(self):
"""Test the base Node generate_build_dict() method"""
n = SCons.Node.Node()
# what line in what file created the node, for example).
Annotate(self)
+ def get_suffix(self):
+ return ''
+
def generate_build_dict(self):
"""Return an appropriate dictionary of values for building
this Node."""
sys.exit(0)
progress_display("scons: done reading SConscript files.")
+ # Tell the Node.FS subsystem that we're all done reading the
+ # SConscript files and calling Repository() and BuildDir() and the
+ # like, so it can go ahead and start memoizing the string values of
+ # file system nodes.
+ SCons.Node.FS.save_strings(1)
+
if not memory_stats is None: memory_stats.append(SCons.Debug.memory())
fs.chdir(fs.Top)