2 # Copyright 2009, Google Inc.
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions are
9 # * Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer.
11 # * Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following disclaimer
13 # in the documentation and/or other materials provided with the
15 # * Neither the name of Google Inc. nor the names of its
16 # contributors may be used to endorse or promote products derived from
17 # this software without specific prior written permission.
19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 """Optional usage logging for Software Construction Toolkit."""
36 # GOOGLE_CHANGE(rspangler) - FROM THIS:
38 # GOOGLE_CHANGE(rspangler) - END CHANGE.
46 chain_build_targets
= None # Previous SCons _build_targets function
48 #------------------------------------------------------------------------------
49 # Wrappers and hooks into SCons
52 class ProgressDisplayWrapper(object):
53 """Wrapper around SCons.Util.DisplayEngine.
55 Needs to be has-a not is-a, since DisplayEngine.set_mode() overrides the
59 def __init__(self
, old_display
):
63 old_display: Old display object to chain to.
65 self
.old_display
= old_display
67 def __call__(self
, text
, append_newline
=1):
71 text: Text to display.
72 append_newline: Append newline to text if non-zero.
75 Passthru from old display object.
77 log
.AddEntry('progress %s' % text
)
78 return self
.old_display(text
, append_newline
)
80 def set_mode(self
, mode
):
81 """Passthru to DisplayEngine.setmode().
84 mode: If non-zero, print progress.
87 Passthru from old display object.
89 return self
.old_display
.set_mode(mode
)
92 def BuildTargetsWrapper(fs
, options
, targets
, target_top
):
93 """Wrapper around SCons.Script.Main._build_targets().
96 fs: Filesystem object.
97 options: SCons options (after modification by SConscripts.
98 targets: Targets to build.
99 target_top: Passed through to _build_targets().
101 log
.AddEntry('build_targets start')
102 log
.SetParam('build_targets.targets', map(str, targets
))
104 # Get list of non-default options. SConscript settings override defaults.
105 build_opts
= dict(options
.__SConscript
_settings
__)
106 # Command line settings are direct attrs, and override SConscript settings.
107 for key
in dir(options
):
108 if key
.startswith('__') or key
== 'settable':
110 value
= getattr(options
, key
)
113 build_opts
[key
] = value
115 for key
, value
in build_opts
.items():
116 log
.SetParam('build_targets.option.%s' % key
, value
)
120 if chain_build_targets
:
121 returnval
= chain_build_targets(fs
, options
, targets
, target_top
)
124 log
.AddEntry('build_targets done')
127 def PrecmdWrapper(self
, line
):
128 """Pre-command handler for SCons.Script.Interactive() to support logging.
132 line: Command line which will be executed.
135 Passthru value of line.
137 log
.AddEntry('Interactive start')
138 log
.SetParam('interactive.command', line
or self
.lastcmd
)
142 def PostcmdWrapper(self
, stop
, line
):
143 """Post-command handler for SCons.Script.Interactive() to support logging.
147 stop: Will execution stop after this function exits?
148 line: Command line which was executed.
151 Passthru value of stop.
153 log
.AddEntry('Interactive done')
158 #------------------------------------------------------------------------------
163 """Usage log object."""
169 self
.dump_writer
= None
170 self
.time
= time
.time
172 def SetParam(self
, key
, value
):
176 key: Parameter name (string).
177 value: Value for parameter.
179 self
.params
[key
] = value
181 def AddEntry(self
, text
):
182 """Adds a timestamped log entry.
185 text: Text of log entry.
187 self
.entries
.append((self
.time(), text
))
189 def ConvertToXml(self
):
190 """Converts the usage log to XML.
193 An xml.dom.minidom.Document object with the usage log contents.
195 xml_impl
= xml
.dom
.getDOMImplementation()
196 xml_doc
= xml_impl
.createDocument(None, 'usage_log', None)
199 xml_param_list
= xml_doc
.createElement('param_list')
200 xml_doc
.documentElement
.appendChild(xml_param_list
)
201 for key
in sorted(self
.params
):
202 xml_param
= xml_doc
.createElement('param')
203 xml_param
.setAttribute('name', str(key
))
204 xml_param_list
.appendChild(xml_param
)
206 value
= self
.params
[key
]
207 if hasattr(value
, '__iter__'):
208 # Iterable value, so list items
210 xml_item
= xml_doc
.createElement('item')
211 xml_item
.setAttribute('value', str(v
))
212 xml_param
.appendChild(xml_item
)
214 # Non-iterable, so convert to string
215 xml_param
.setAttribute('value', str(value
))
218 xml_entry_list
= xml_doc
.createElement('entry_list')
219 xml_doc
.documentElement
.appendChild(xml_entry_list
)
220 for entry_time
, entry_text
in self
.entries
:
221 xml_entry
= xml_doc
.createElement('entry')
222 xml_entry
.setAttribute('time', str(entry_time
))
223 xml_entry
.setAttribute('text', str(entry_text
))
224 xml_entry_list
.appendChild(xml_entry
)
229 """Dumps the log by calling self.dump_writer(), then clears the log."""
231 self
.dump_writer(self
)
233 # Clear log entries (but not params, since they can be used again if SCons
234 # is in interactive mode).
238 def SetOutputFile(self
, filename
):
239 """Sets the output filename for usage log dumps.
242 filename: Name of output file.
244 self
.dump_to_file
= filename
245 self
.dump_writer
= FileDumpWriter
247 #------------------------------------------------------------------------------
250 def AddSystemParams():
251 """Prints system stats."""
252 log
.SetParam('sys.argv', sys
.argv
)
253 log
.SetParam('sys.executable', sys
.executable
)
254 log
.SetParam('sys.version', sys
.version
)
255 log
.SetParam('sys.version_info', sys
.version_info
)
256 log
.SetParam('sys.path', sys
.path
)
257 log
.SetParam('sys.platform', sys
.platform
)
258 # GOOGLE_CHANGE(rspangler) - FROM THIS:
259 # log.SetParam('platform.uname', platform.uname())
260 # log.SetParam('platform.platform', platform.platform())
261 # GOOGLE_CHANGE(rspangler) - END CHANGE.
263 for e
in ['PATH', 'INCLUDE', 'LIB', 'HAMMER_OPTS', 'HAMMER_XGE']:
264 log
.SetParam('shell.%s' % e
, os
.environ
.get(e
, ''))
266 log
.SetParam('scons.version', SCons
.__version
__)
270 """Usage log cleanup at exit."""
271 log
.AddEntry('usage_log exit')
276 """Code executed at module load time."""
279 # Wrap SCons' progress display wrapper
280 SCons
.Script
.Main
.progress_display
= ProgressDisplayWrapper(
281 SCons
.Script
.Main
.progress_display
)
283 # Wrap SCons' _build_targets()
284 global chain_build_targets
285 chain_build_targets
= SCons
.Script
.Main
._build
_targets
286 SCons
.Script
.Main
._build
_targets
= BuildTargetsWrapper
288 # Hook SCons interactive mode
289 SCons
.Script
.Interactive
.SConsInteractiveCmd
.precmd
= PrecmdWrapper
290 SCons
.Script
.Interactive
.SConsInteractiveCmd
.postcmd
= PostcmdWrapper
292 # Make sure we get called at exit
293 atexit
.register(AtExit
)
296 def FileDumpWriter(log
):
297 """Dumps the log to the specified file."""
298 print 'Writing usage log to %s...' % log
.dump_to_file
299 f
= open(log
.dump_to_file
, 'wt')
300 doc
= log
.ConvertToXml()
301 doc
.writexml(f
, encoding
='UTF-8', addindent
=' ', newl
='\n')
304 print 'Done writing log.'
307 # Create the initial log (can't do this in AtModuleLoad() without 'global')
309 log
.AddEntry('usage_log loaded')
311 # Do other work at module load time