blob: e043ca9fc20421f6893c4dc98ebb5dd60b16c6ee [file] [log] [blame]
#!/usr/bin/python2.4
# Copyright 2009 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Optional usage logging for Software Construction Toolkit."""
from __future__ import print_function
import atexit
import os
import platform
import sys
import time
import xml.dom
import SCons
import SCons.Script
chain_build_targets = None # Previous SCons _build_targets function
#------------------------------------------------------------------------------
# Wrappers and hooks into SCons
class ProgressDisplayWrapper(object):
"""Wrapper around SCons.Util.DisplayEngine.
Needs to be has-a not is-a, since DisplayEngine.set_mode() overrides the
__call__ member.
"""
def __init__(self, old_display):
"""Constructor.
Args:
old_display: Old display object to chain to.
"""
self.old_display = old_display
def __call__(self, text, append_newline=1):
"""Display progress.
Args:
text: Text to display.
append_newline: Append newline to text if non-zero.
Returns:
Passthru from old display object.
"""
log.AddEntry('progress %s' % text)
return self.old_display(text, append_newline)
def set_mode(self, mode):
"""Passthru to DisplayEngine.setmode().
Args:
mode: If non-zero, print progress.
Returns:
Passthru from old display object.
"""
return self.old_display.set_mode(mode)
def BuildTargetsWrapper(fs, options, targets, target_top):
"""Wrapper around SCons.Script.Main._build_targets().
Args:
fs: Filesystem object.
options: SCons options (after modification by SConscripts.
targets: Targets to build.
target_top: Passed through to _build_targets().
"""
log.AddEntry('build_targets start')
log.SetParam('build_targets.targets', map(str, targets))
# Get list of non-default options. SConscript settings override defaults.
build_opts = dict(options.__SConscript_settings__)
# Command line settings are direct attrs, and override SConscript settings.
for key in dir(options):
if key.startswith('__') or key == 'settable':
continue
value = getattr(options, key)
if callable(value):
continue
build_opts[key] = value
for key, value in build_opts.items():
log.SetParam('build_targets.option.%s' % key, value)
try:
returnval = None
if chain_build_targets:
returnval = chain_build_targets(fs, options, targets, target_top)
return returnval
finally:
log.AddEntry('build_targets done')
def PrecmdWrapper(self, line):
"""Pre-command handler for SCons.Script.Interactive() to support logging.
Args:
self: cmd object.
line: Command line which will be executed.
Returns:
Passthru value of line.
"""
log.AddEntry('Interactive start')
log.SetParam('interactive.command', line or self.lastcmd)
return line
def PostcmdWrapper(self, stop, line):
"""Post-command handler for SCons.Script.Interactive() to support logging.
Args:
self: cmd object.
stop: Will execution stop after this function exits?
line: Command line which was executed.
Returns:
Passthru value of stop.
"""
log.AddEntry('Interactive done')
log.Dump()
return stop
#------------------------------------------------------------------------------
# Usage log object
class Log(object):
"""Usage log object."""
def __init__(self):
"""Constructor."""
self.params = {}
self.entries = []
self.dump_writer = None
self.time = time.time
def SetParam(self, key, value):
"""Sets a parameter.
Args:
key: Parameter name (string).
value: Value for parameter.
"""
self.params[key] = value
def AddEntry(self, text):
"""Adds a timestamped log entry.
Args:
text: Text of log entry.
"""
self.entries.append((self.time(), text))
def ConvertToXml(self):
"""Converts the usage log to XML.
Returns:
An xml.dom.minidom.Document object with the usage log contents.
"""
xml_impl = xml.dom.getDOMImplementation()
xml_doc = xml_impl.createDocument(None, 'usage_log', None)
# List build params
xml_param_list = xml_doc.createElement('param_list')
xml_doc.documentElement.appendChild(xml_param_list)
for key in sorted(self.params):
xml_param = xml_doc.createElement('param')
xml_param.setAttribute('name', str(key))
xml_param_list.appendChild(xml_param)
value = self.params[key]
if hasattr(value, '__iter__'):
# Iterable value, so list items
for v in value:
xml_item = xml_doc.createElement('item')
xml_item.setAttribute('value', str(v))
xml_param.appendChild(xml_item)
else:
# Non-iterable, so convert to string
xml_param.setAttribute('value', str(value))
# List log entries
xml_entry_list = xml_doc.createElement('entry_list')
xml_doc.documentElement.appendChild(xml_entry_list)
for entry_time, entry_text in self.entries:
xml_entry = xml_doc.createElement('entry')
xml_entry.setAttribute('time', str(entry_time))
xml_entry.setAttribute('text', str(entry_text))
xml_entry_list.appendChild(xml_entry)
return xml_doc
def Dump(self):
"""Dumps the log by calling self.dump_writer(), then clears the log."""
if self.dump_writer:
self.dump_writer(self)
# Clear log entries (but not params, since they can be used again if SCons
# is in interactive mode).
self.entries = []
def SetOutputFile(self, filename):
"""Sets the output filename for usage log dumps.
Args:
filename: Name of output file.
"""
self.dump_to_file = filename
self.dump_writer = FileDumpWriter
#------------------------------------------------------------------------------
# Usage log methods
def AddSystemParams():
"""Prints system stats."""
log.SetParam('sys.argv', sys.argv)
log.SetParam('sys.executable', sys.executable)
log.SetParam('sys.version', sys.version)
log.SetParam('sys.version_info', sys.version_info)
log.SetParam('sys.path', sys.path)
log.SetParam('sys.platform', sys.platform)
log.SetParam('platform.uname', platform.uname())
log.SetParam('platform.platform', platform.platform())
for e in ['PATH', 'INCLUDE', 'LIB', 'HAMMER_OPTS', 'HAMMER_XGE']:
log.SetParam('shell.%s' % e, os.environ.get(e, ''))
log.SetParam('scons.version', SCons.__version__)
def AtExit():
"""Usage log cleanup at exit."""
log.AddEntry('usage_log exit')
log.Dump()
def AtModuleLoad():
"""Code executed at module load time."""
AddSystemParams()
# Wrap SCons' progress display wrapper
SCons.Script.Main.progress_display = ProgressDisplayWrapper(
SCons.Script.Main.progress_display)
# Wrap SCons' _build_targets()
global chain_build_targets
chain_build_targets = SCons.Script.Main._build_targets
SCons.Script.Main._build_targets = BuildTargetsWrapper
# Hook SCons interactive mode
SCons.Script.Interactive.SConsInteractiveCmd.precmd = PrecmdWrapper
SCons.Script.Interactive.SConsInteractiveCmd.postcmd = PostcmdWrapper
# Make sure we get called at exit
atexit.register(AtExit)
def FileDumpWriter(log):
"""Dumps the log to the specified file."""
print('Writing usage log to %s...' % log.dump_to_file)
f = open(log.dump_to_file, 'wt')
doc = log.ConvertToXml()
doc.writexml(f, encoding='UTF-8', addindent=' ', newl='\n')
doc.unlink()
f.close()
print('Done writing log.')
# Create the initial log (can't do this in AtModuleLoad() without 'global')
log = Log()
log.AddEntry('usage_log loaded')
# Do other work at module load time
AtModuleLoad()