Chromium Code Reviews| Index: tools/clang/scripts/run_tool.py |
| diff --git a/tools/clang/scripts/run_tool.py b/tools/clang/scripts/run_tool.py |
| new file mode 100755 |
| index 0000000000000000000000000000000000000000..6245b78025bb97fac3c33c52ae8a5d6156633bca |
| --- /dev/null |
| +++ b/tools/clang/scripts/run_tool.py |
| @@ -0,0 +1,144 @@ |
| +#!/usr/bin/env python |
| +# Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| +# Use of this source code is governed by a BSD-style license that can be |
| +# found in the LICENSE file. |
|
Nico
2013/03/27 16:35:29
Doesn't say why this is used instead of tooling's
dcheng
2013/03/28 00:10:17
Done now =)
|
| + |
| +import collections |
| +import functools |
| +import multiprocessing |
| +import subprocess |
| +import sys |
| + |
| + |
| +"""Represents an edit to a file.""" |
| +Edit= collections.namedtuple( |
| + 'Edit', ('edit_type', 'offset', 'length', 'replacement')) |
| + |
| + |
| +"""Gets the list of files to run the tool over from git.""" |
| +def _GetFilesFromGit(paths = None): |
| + try: |
| + args = ['git', 'ls-files'] |
| + if paths: |
| + args.extend(paths) |
| + command = subprocess.Popen(args, stdout=subprocess.PIPE) |
| + output, _ = command.communicate() |
| + return [f for f in output.splitlines() if f[-3:] == '.cc'] |
|
Nico
2013/03/27 16:35:29
f.endswith('.cc')
(there's also .m, .mm, .cpp, .h
dcheng
2013/03/28 00:10:17
1) I didn't really want to parse compile_commands.
|
| + except OSError: |
| + # TODO(dcheng): Handle errors. |
| + sys.exit(1) |
| + |
| + |
| +"""Executes the tool. |
| + |
| +This is outside the class so it can be pickled.""" |
| +def _ExecuteTool(toolname, build_directory, filename): |
| + # TODO(dcheng): Scrape output and handle errors. |
| + command = subprocess.Popen((toolname, '-p', build_directory, filename), |
| + stdout=subprocess.PIPE, |
| + stderr=subprocess.PIPE) |
| + stdout, stderr = command.communicate() |
| + if command.returncode != 0: |
| + return {'status': False, 'filename': filename, 'stderr': stderr} |
| + else: |
| + return {'status': True, 'stdout': stdout} |
| + |
| + |
| +"""God class that does everything.""" |
| +class _CompilerDispatcher(object): |
| + def __init__(self, toolname, build_directory, filenames): |
| + self.__toolname = toolname |
| + self.__build_directory = build_directory |
| + self.__filenames = filenames |
| + self.__success_count = 0 |
| + self.__failed_count = 0 |
| + self.__edits = collections.defaultdict(list) |
| + |
| + @property |
| + def edits(self): |
| + return self.__edits |
| + |
| + """God function that does everything.""" |
| + def Run(self): |
| + # TODO(dcheng): Add some timing. |
| + pool = multiprocessing.Pool() |
| + result_iterator = pool.imap_unordered( |
| + functools.partial(_ExecuteTool, self.__toolname, |
| + self.__build_directory), |
| + self.__filenames) |
| + for result in result_iterator: |
| + self.__ProcessResult(result) |
| + sys.stdout.write('\n') |
| + sys.stdout.flush() |
| + |
| + """Process the result.""" |
| + def __ProcessResult(self, result): |
| + if result['status']: |
| + self.__success_count += 1 |
| + self.__AddEditsFromStdout(result['stdout']) |
| + else: |
| + self.__failed_count += 1 |
| + sys.stdout.write('\nFailed to process %s\n' % result['filename']) |
| + sys.stdout.write(result['stderr']) |
| + sys.stdout.write('\n') |
| + percentage = ( |
| + float(self.__success_count + self.__failed_count) / |
| + len(self.__filenames)) * 100 |
| + sys.stdout.write('Succeeded: %d, Failed: %d [%.2f%%]\r' % ( |
| + self.__success_count, self.__failed_count, percentage)) |
| + sys.stdout.flush() |
| + |
| + """Extracts and add the list of edits generated on the tool's stdout.""" |
| + def __AddEditsFromStdout(self, stdout): |
| + lines = stdout.splitlines() |
| + start_index = lines.index('==== BEGIN EDITS ====') |
| + end_index = lines.index('==== END EDITS ====') |
| + for line in lines[start_index + 1:end_index]: |
| + edit_type, path, offset, length, replacement = line.split(':', 4) |
| + # TODO(dcheng): [6:] is a horrible hack to trim off ../../ and is fragile. |
| + self.__edits[path[6:]].append( |
| + Edit(edit_type, int(offset), int(length), replacement)) |
| + |
| + |
| +"""Applies the edits!""" |
| +def _ApplyEdits(edits): |
| + edit_count = 0 |
| + for k, v in edits.iteritems(): |
| + # Sort the edits and iterate through them in reverse order. Sorting allows |
| + # duplicate edits to be quickly skipped, while reversing means that |
| + # subsequent edits don't need to have their offsets updated with each edit |
| + # applied. |
| + v.sort() |
| + last_edit = None |
| + with open(k, 'r+') as f: |
| + contents = f.read() |
| + for edit in reversed(v): |
| + if edit == last_edit: |
| + continue |
| + last_edit = edit |
| + # TODO(dcheng): Would bytearray be better? |
| + contents = (contents[:edit.offset] + |
| + edit.replacement + |
| + contents[edit.offset + edit.length:]) |
| + edit_count += 1 |
| + f.seek(0) |
| + f.truncate() |
| + f.write(contents) |
| + print 'Applied %d edits to %d files' % (edit_count, len(edits)) |
| + |
| + |
| +def main(argv): |
| + # TODO(dcheng): Assert that we're running from chrome/src. |
| + filenames = frozenset(_GetFilesFromGit(argv[2:])) |
| + dispatcher = _CompilerDispatcher(argv[0], argv[1], filenames) |
| + dispatcher.Run() |
| + # Filter out any files that aren't in the git repository, since it's not |
| + # useful to modify files that aren't under source control--typically, these |
| + # are generated files or files in a git submodule that's not part of Chrome. |
| + filtered_edits = {k : v for k, v in dispatcher.edits.iteritems() |
| + if k in filenames} |
| + _ApplyEdits(filtered_edits) |
| + |
| + |
| +if __name__ == '__main__': |
| + sys.exit(main(sys.argv[1:])) |