./SCons/Action.py
./SCons/Builder.py
./SCons/Conftest.py
+./SCons/Debug.py
./SCons/Defaults.py
./SCons/Environment.py
./SCons/Errors.py
.I type
specifies what type of debugging:
+.TP
+--debug=count
+Print a count of how many objects are created
+of the various classes used internally by SCons.
+This only works when run under Python 2.1 or later.
+
+.TP
+--debug=dtree
+Print the dependency tree
+after each top-level target is built. This prints out only derived files.
+
+.TP
+--debug=includes
+Print the include tree after each top-level target is built.
+This is generally used to find out what files are included by the sources
+of a given derived file:
+
+.ES
+$ scons --debug=includes foo.o
+.EE
+
+.TP
+--debug=memory
+Prints how much memory SCons uses
+before and after reading the SConscript files
+and before and after building.
+
+.TP
+--debug=objects
+Prints a list of the various objects
+of the various classes used internally by SCons.
+This only works when run under Python 2.1 or later.
+
.TP
--debug=pdb
Re-run SCons under the control of the
but all other arguments will be passed in-order
to the SCons invocation run by the debugger.
-.TP
---debug=tree
-Print the dependency tree
-after each top-level target is built. This prints out the complete
-dependency tree including implicit dependencies and ignored
-dependencies.
-
-.TP
---debug=dtree
-Print the dependency tree
-after each top-level target is built. This prints out only derived files.
-
.TP
--debug=time
Prints various time profiling information: the time spent
SConscript files, and the total time spent executing SCons itself.
.TP
---debug=includes
-Print the include tree after each top-level target is built.
-This is generally used to find out what files are included by the sources
-of a given derived file:
-
-.ES
-$ scons --debug=includes foo.o
-.EE
+--debug=tree
+Print the dependency tree
+after each top-level target is built. This prints out the complete
+dependency tree including implicit dependencies and ignored
+dependencies.
.\" .TP
.\" -e, --environment-overrides
- Fix transparent checkout of implicit dependency files from SCCS
and RCS.
+ - Added new --debug=count, --debug=memory and --debug=objects options.
+ --debug=count and --debug=objects only print anything when run
+ under Python 2.1 or later.
+
From Vincent Risi:
- Add support for the bcc32, ilink32 and tlib Borland tools.
SCons/Action.py
SCons/Builder.py
SCons/Conftest.py
+SCons/Debug.py
SCons/Defaults.py
SCons/Environment.py
SCons/Errors.py
import string
import sys
+from SCons.Debug import logInstanceCreation
import SCons.Errors
import SCons.Util
# Cmd list can actually be a list or a single item...basically
# anything that we could pass in as the first arg to
# Environment.subst_list().
+ if __debug__: logInstanceCreation(self)
self.cmd_list = cmd
def strfunction(self, target, source, env):
class CommandGeneratorAction(ActionBase):
"""Class for command-generator actions."""
def __init__(self, generator):
+ if __debug__: logInstanceCreation(self)
self.generator = generator
def __generate(self, target, source, env, for_signature):
until execution time to see what type it is, then tries to
create an Action out of it."""
def __init__(self, var):
+ if __debug__: logInstanceCreation(self)
self.var = SCons.Util.to_String(var)
def strfunction(self, target, source, env):
"""Class for Python function actions."""
def __init__(self, execfunction, strfunction=_null, varlist=[]):
+ if __debug__: logInstanceCreation(self)
self.execfunction = execfunction
if strfunction is _null:
def strfunction(target, source, env, execfunction=execfunction):
class ListAction(ActionBase):
"""Class for lists of other actions."""
def __init__(self, list):
+ if __debug__: logInstanceCreation(self)
self.list = map(lambda x: Action(x), list)
def get_actions(self):
import UserDict
import SCons.Action
+from SCons.Debug import logInstanceCreation
from SCons.Errors import InternalError, UserError
import SCons.Executor
import SCons.Node.FS
multi = 0,
env = None,
overrides = {}):
+ if __debug__: logInstanceCreation(self, 'BuilderBase')
self.action = SCons.Action.Action(action)
self.multi = multi
if SCons.Util.is_Dict(prefix):
if not t.is_derived():
t.builder = self
new_targets.append(t)
-
+
target, source = self.emitter(target=tlist, source=slist, env=env)
# Now delete the temporary builders that we attached to any
"""
def __init__(self, builder, env, tlist):
+ if __debug__: logInstanceCreation(self)
SCons.Util.Proxy.__init__(self, builder)
self.builder = builder
self.scanner = builder.scanner
source_factory = None,
scanner=None,
emitter=None):
+ if __debug__: logInstanceCreation(self)
BuilderBase.__init__(self, action, prefix, suffix, src_suffix,
node_factory, target_factory, source_factory,
scanner, emitter)
"""
def __init__(self, builder, cmdgen):
+ if __debug__: logInstanceCreation(self)
SCons.Util.Proxy.__init__(self, builder)
# cmdgen should always be an instance of DictCmdGenerator.
def add_action(self, suffix, action):
self.cmdgen.add_action(suffix, action)
self.set_src_suffix(self.cmdgen.src_suffixes())
-
+
def __cmp__(self, other):
return cmp(self.__dict__, other.__dict__)
--- /dev/null
+"""SCons.Debug
+
+Code for debugging SCons internal things. Not everything here is
+guaranteed to work all the way back to Python 1.5.2, and shouldn't be
+needed by most users.
+
+"""
+
+#
+# __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__"
+
+
+# Recipe 14.10 from the Python Cookbook.
+import string
+import sys
+try:
+ import weakref
+except ImportError:
+ def logInstanceCreation(instance, name=None):
+ pass
+else:
+ def logInstanceCreation(instance, name=None):
+ if name is None:
+ name = instance.__class__.__name__
+ if not tracked_classes.has_key(name):
+ tracked_classes[name] = []
+ tracked_classes[name].append(weakref.ref(instance))
+
+
+
+tracked_classes = {}
+
+def string_to_classes(s):
+ if s == '*':
+ c = tracked_classes.keys()
+ c.sort()
+ return c
+ else:
+ return string.split(s)
+
+def countLoggedInstances(classes, file=sys.stdout):
+ for classname in string_to_classes(classes):
+ file.write("%s: %d\n" % (classname, len(tracked_classes[classname])))
+
+def listLoggedInstances(classes, file=sys.stdout):
+ for classname in string_to_classes(classes):
+ file.write('\n%s:\n' % classname)
+ for ref in tracked_classes[classname]:
+ obj = ref()
+ if obj is not None:
+ file.write(' %s\n' % repr(obj))
+
+def dumpLoggedInstances(classes, file=sys.stdout):
+ for classname in string_to_classes(classes):
+ file.write('\n%s:\n' % classname)
+ for ref in tracked_classes[classname]:
+ obj = ref()
+ if obj is not None:
+ file.write(' %s:\n' % obj)
+ for key, value in obj.__dict__.items():
+ file.write(' %20s : %s\n' % (key, value))
+
+
+
+if sys.platform[:5] == "linux":
+ # Linux doesn't actually support memory usage stats from getrusage().
+ def memory():
+ mstr = open('/proc/self/stat').read()
+ mstr = string.split(mstr)[22]
+ return int(mstr)
+else:
+ try:
+ import resource
+ except ImportError:
+ def memory():
+ return 0
+ else:
+ def memory():
+ res = resource.getrusage(resource.RUSAGE_SELF)
+ return res[4]
import SCons.Action
import SCons.Builder
+from SCons.Debug import logInstanceCreation
import SCons.Defaults
import SCons.Errors
import SCons.Node
toolpath=[],
options=None,
**kw):
+ if __debug__: logInstanceCreation(self)
self.fs = SCons.Node.FS.default_fs
self.ans = SCons.Node.Alias.default_ans
self.lookup_list = SCons.Node.arg2nodes_lookups
suffix - construction variable for the suffix.
"""
- suffix = self.subst('$%s'%suffix)
- prefix = self.subst('$%s'%prefix)
+ suffix = self.subst('$'+suffix)
+ prefix = self.subst('$'+prefix)
for path in paths:
dir,name = os.path.split(str(path))
new_prefix - construction variable for the new prefix.
new_suffix - construction variable for the new suffix.
"""
- old_prefix = self.subst('$%s'%old_prefix)
- old_suffix = self.subst('$%s'%old_suffix)
+ old_prefix = self.subst('$'+old_prefix)
+ old_suffix = self.subst('$'+old_suffix)
- new_prefix = self.subst('$%s'%new_prefix)
- new_suffix = self.subst('$%s'%new_suffix)
+ new_prefix = self.subst('$'+new_prefix)
+ new_suffix = self.subst('$'+new_suffix)
dir,name = os.path.split(str(path))
if name[:len(old_prefix)] == old_prefix:
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+from SCons.Debug import logInstanceCreation
+
+
class Executor:
"""A class for controlling instances of executing an action.
"""
def __init__(self, builder, env, overrides, targets, sources):
+ if __debug__: logInstanceCreation(self)
self.builder = builder
self.env = env
self.overrides = overrides
import cStringIO
import SCons.Action
+from SCons.Debug import logInstanceCreation
import SCons.Errors
import SCons.Node
-import SCons.Util
import SCons.Sig.MD5
+import SCons.Util
import SCons.Warnings
#
our relative and absolute paths, identify our parent
directory, and indicate that this node should use
signatures."""
+ if __debug__: logInstanceCreation(self, 'Node.FS.Base')
SCons.Node.Node.__init__(self)
self.name = name
The path argument must be a valid absolute path.
"""
+ if __debug__: logInstanceCreation(self)
if path == None:
self.pathTop = os.getcwd()
else:
"""
def __init__(self, name, directory, fs):
+ if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
Base.__init__(self, name, directory, fs)
self._morph()
"""A class for files in a file system.
"""
def __init__(self, name, directory, fs):
+ if __debug__: logInstanceCreation(self, 'Node.FS.File')
Base.__init__(self, name, directory, fs)
self._morph()
"""Search for a list of directories in the Repository list."""
return self.fs.Rsearchall(pathlist, clazz=Dir, must_exist=0,
cwd=self.cwd)
-
+
def generate_build_env(self, env):
"""Generate an appropriate Environment to build this File."""
return env.Override({'Dir' : self.Dir,
'File' : self.File,
'RDirs' : self.RDirs})
-
+
def _morph(self):
"""Turn a file system node into a File object."""
self.scanner_paths = {}
import copy
+from SCons.Debug import logInstanceCreation
import SCons.Sig
import SCons.Util
pass
def __init__(self):
+ if __debug__: logInstanceCreation(self, 'Node')
# Note that we no longer explicitly initialize a self.builder
# attribute to None here. That's because the self.builder
# attribute may be created on-the-fly later by a subclass (the
except AttributeError:
if not create:
raise
- import SCons.Builder
+ import SCons.Executor
env = self.generate_build_env(self.builder.env)
executor = SCons.Executor.Executor(self.builder,
env,
# 'lib',
# 'scons-%d' % SCons.__version__)] + sys.path[1:]
+import SCons.Debug
import SCons.Defaults
import SCons.Environment
import SCons.Errors
display = SCons.Util.display
progress_display = SCons.Util.DisplayEngine()
-#
# Task control.
#
class BuildTask(SCons.Taskmaster.Task):
# Global variables
keep_going_on_error = 0
-print_tree = 0
+print_count = 0
print_dtree = 0
-print_time = 0
print_includes = 0
+print_objects = 0
+print_time = 0
+print_tree = 0
+memory_stats = None
ignore_errors = 0
sconscript_time = 0
command_time = 0
return None
def _set_globals(options):
- global repositories, keep_going_on_error, print_tree, print_dtree
- global print_time, ignore_errors, print_includes
+ global repositories, keep_going_on_error, ignore_errors
+ global print_count, print_dtree, print_includes
+ global print_objects, print_time, print_tree
+ global memory_outf, memory_stats
if options.repository:
repositories.extend(options.repository)
keep_going_on_error = options.keep_going
try:
if options.debug:
- if options.debug == "tree":
- print_tree = 1
+ if options.debug == "count":
+ print_count = 1
elif options.debug == "dtree":
print_dtree = 1
- elif options.debug == "time":
- print_time = 1
elif options.debug == "includes":
print_includes = 1
+ elif options.debug == "memory":
+ memory_stats = []
+ memory_outf = sys.stdout
+ elif options.debug == "objects":
+ print_objects = 1
+ elif options.debug == "time":
+ print_time = 1
+ elif options.debug == "tree":
+ print_tree = 1
except AttributeError:
pass
ignore_errors = options.ignore_errors
"build all Default() targets.")
def opt_debug(option, opt, value, parser):
- if value in ["pdb","tree", "dtree", "time", "includes"]:
+ if value in ["count", "dtree", "includes", "memory", "objects", "pdb", "time", "tree"]:
setattr(parser.values, 'debug', value)
else:
raise OptionValueError("Warning: %s is not a valid debug type" % value)
callback=opt_debug, nargs=1, dest="debug",
metavar="TYPE",
help="Print various types of debugging information: "
- "pdb, tree, dtree, time, or includes.")
+ "count, dtree, includes, memory, objects, pdb, time, tree.")
self.add_option('-f', '--file', '--makefile', '--sconstruct',
action="append", nargs=1,
for rep in repositories:
fs.Repository(rep)
+ if not memory_stats is None: memory_stats.append(SCons.Debug.memory())
+
progress_display("scons: Reading SConscript files ...")
try:
start_time = time.time()
sys.exit(0)
progress_display("scons: done reading SConscript files.")
+ if not memory_stats is None: memory_stats.append(SCons.Debug.memory())
+
fs.chdir(fs.Top)
if options.help_msg:
"\tignoring -j or num_jobs option.\n"
SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
+ if not memory_stats is None: memory_stats.append(SCons.Debug.memory())
+
try:
jobs.run()
finally:
if not options.noexec:
SCons.Sig.write()
+ if not memory_stats is None:
+ memory_stats.append(SCons.Debug.memory())
+ when = [
+ 'before SConscript files',
+ 'after SConscript files',
+ 'before building',
+ 'after building',
+ ]
+ for i in xrange(len(when)):
+ memory_outf.write('Memory %s: %d\n' % (when[i], memory_stats[i]))
+
+ if print_count:
+ SCons.Debug.countLoggedInstances('*')
+
+ if print_objects:
+ SCons.Debug.listLoggedInstances('*')
+ #SCons.Debug.dumpLoggedInstances('*')
+
def _exec_main():
all_args = sys.argv[1:]
try:
assert check(total_time, sconscript_time+scons_time+command_time, 0.01)
assert check(total_time, expected_total_time, 0.1)
-test.pass_test()
+try:
+ import resource
+except ImportError:
+ print "Python version has no `resource' module;"
+ print "skipping test of --debug=memory."
+else:
+ ############################
+ # test --debug=memory
+
+ test.run(arguments = "--debug=memory")
+ lines = string.split(test.stdout(), '\n')
+ test.fail_test(re.match(r'Memory before SConscript files: \d+', lines[-5]) is None)
+ test.fail_test(re.match(r'Memory after SConscript files: \d+', lines[-4]) is None)
+ test.fail_test(re.match(r'Memory before building: \d+', lines[-3]) is None)
+ test.fail_test(re.match(r'Memory after building: \d+', lines[-2]) is None)
+
+try:
+ import weakref
+except ImportError:
+ print "Python version has no `weakref' module;"
+ print "skipping tests of --debug=count and --debug=objects."
+else:
+ ############################
+ # test --debug=count
+ # Just check that object counts for some representative classes
+ # show up in the output.
+ test.run(arguments = "--debug=count")
+ stdout = test.stdout()
+ test.fail_test(re.search('BuilderBase: \d+', stdout) is None)
+ test.fail_test(re.search('FS: \d+', stdout) is None)
+ test.fail_test(re.search('Node: \d+', stdout) is None)
+ test.fail_test(re.search('SConsEnvironment: \d+', stdout) is None)
+
+ ############################
+ # test --debug=objects
+ # Just check that it runs, we're not overly concerned about the actual
+ # output at this point.
+ test.run(arguments = "--debug=objects")
+
+test.pass_test()