Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/env python | |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # 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 =)
| |
| 5 | |
| 6 import collections | |
| 7 import functools | |
| 8 import multiprocessing | |
| 9 import subprocess | |
| 10 import sys | |
| 11 | |
| 12 | |
| 13 """Represents an edit to a file.""" | |
| 14 Edit= collections.namedtuple( | |
| 15 'Edit', ('edit_type', 'offset', 'length', 'replacement')) | |
| 16 | |
| 17 | |
| 18 """Gets the list of files to run the tool over from git.""" | |
| 19 def _GetFilesFromGit(paths = None): | |
| 20 try: | |
| 21 args = ['git', 'ls-files'] | |
| 22 if paths: | |
| 23 args.extend(paths) | |
| 24 command = subprocess.Popen(args, stdout=subprocess.PIPE) | |
| 25 output, _ = command.communicate() | |
| 26 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.
| |
| 27 except OSError: | |
| 28 # TODO(dcheng): Handle errors. | |
| 29 sys.exit(1) | |
| 30 | |
| 31 | |
| 32 """Executes the tool. | |
| 33 | |
| 34 This is outside the class so it can be pickled.""" | |
| 35 def _ExecuteTool(toolname, build_directory, filename): | |
| 36 # TODO(dcheng): Scrape output and handle errors. | |
| 37 command = subprocess.Popen((toolname, '-p', build_directory, filename), | |
| 38 stdout=subprocess.PIPE, | |
| 39 stderr=subprocess.PIPE) | |
| 40 stdout, stderr = command.communicate() | |
| 41 if command.returncode != 0: | |
| 42 return {'status': False, 'filename': filename, 'stderr': stderr} | |
| 43 else: | |
| 44 return {'status': True, 'stdout': stdout} | |
| 45 | |
| 46 | |
| 47 """God class that does everything.""" | |
| 48 class _CompilerDispatcher(object): | |
| 49 def __init__(self, toolname, build_directory, filenames): | |
| 50 self.__toolname = toolname | |
| 51 self.__build_directory = build_directory | |
| 52 self.__filenames = filenames | |
| 53 self.__success_count = 0 | |
| 54 self.__failed_count = 0 | |
| 55 self.__edits = collections.defaultdict(list) | |
| 56 | |
| 57 @property | |
| 58 def edits(self): | |
| 59 return self.__edits | |
| 60 | |
| 61 """God function that does everything.""" | |
| 62 def Run(self): | |
| 63 # TODO(dcheng): Add some timing. | |
| 64 pool = multiprocessing.Pool() | |
| 65 result_iterator = pool.imap_unordered( | |
| 66 functools.partial(_ExecuteTool, self.__toolname, | |
| 67 self.__build_directory), | |
| 68 self.__filenames) | |
| 69 for result in result_iterator: | |
| 70 self.__ProcessResult(result) | |
| 71 sys.stdout.write('\n') | |
| 72 sys.stdout.flush() | |
| 73 | |
| 74 """Process the result.""" | |
| 75 def __ProcessResult(self, result): | |
| 76 if result['status']: | |
| 77 self.__success_count += 1 | |
| 78 self.__AddEditsFromStdout(result['stdout']) | |
| 79 else: | |
| 80 self.__failed_count += 1 | |
| 81 sys.stdout.write('\nFailed to process %s\n' % result['filename']) | |
| 82 sys.stdout.write(result['stderr']) | |
| 83 sys.stdout.write('\n') | |
| 84 percentage = ( | |
| 85 float(self.__success_count + self.__failed_count) / | |
| 86 len(self.__filenames)) * 100 | |
| 87 sys.stdout.write('Succeeded: %d, Failed: %d [%.2f%%]\r' % ( | |
| 88 self.__success_count, self.__failed_count, percentage)) | |
| 89 sys.stdout.flush() | |
| 90 | |
| 91 """Extracts and add the list of edits generated on the tool's stdout.""" | |
| 92 def __AddEditsFromStdout(self, stdout): | |
| 93 lines = stdout.splitlines() | |
| 94 start_index = lines.index('==== BEGIN EDITS ====') | |
| 95 end_index = lines.index('==== END EDITS ====') | |
| 96 for line in lines[start_index + 1:end_index]: | |
| 97 edit_type, path, offset, length, replacement = line.split(':', 4) | |
| 98 # TODO(dcheng): [6:] is a horrible hack to trim off ../../ and is fragile. | |
| 99 self.__edits[path[6:]].append( | |
| 100 Edit(edit_type, int(offset), int(length), replacement)) | |
| 101 | |
| 102 | |
| 103 """Applies the edits!""" | |
| 104 def _ApplyEdits(edits): | |
| 105 edit_count = 0 | |
| 106 for k, v in edits.iteritems(): | |
| 107 # Sort the edits and iterate through them in reverse order. Sorting allows | |
| 108 # duplicate edits to be quickly skipped, while reversing means that | |
| 109 # subsequent edits don't need to have their offsets updated with each edit | |
| 110 # applied. | |
| 111 v.sort() | |
| 112 last_edit = None | |
| 113 with open(k, 'r+') as f: | |
| 114 contents = f.read() | |
| 115 for edit in reversed(v): | |
| 116 if edit == last_edit: | |
| 117 continue | |
| 118 last_edit = edit | |
| 119 # TODO(dcheng): Would bytearray be better? | |
| 120 contents = (contents[:edit.offset] + | |
| 121 edit.replacement + | |
| 122 contents[edit.offset + edit.length:]) | |
| 123 edit_count += 1 | |
| 124 f.seek(0) | |
| 125 f.truncate() | |
| 126 f.write(contents) | |
| 127 print 'Applied %d edits to %d files' % (edit_count, len(edits)) | |
| 128 | |
| 129 | |
| 130 def main(argv): | |
| 131 # TODO(dcheng): Assert that we're running from chrome/src. | |
| 132 filenames = frozenset(_GetFilesFromGit(argv[2:])) | |
| 133 dispatcher = _CompilerDispatcher(argv[0], argv[1], filenames) | |
| 134 dispatcher.Run() | |
| 135 # Filter out any files that aren't in the git repository, since it's not | |
| 136 # useful to modify files that aren't under source control--typically, these | |
| 137 # are generated files or files in a git submodule that's not part of Chrome. | |
| 138 filtered_edits = {k : v for k, v in dispatcher.edits.iteritems() | |
| 139 if k in filenames} | |
| 140 _ApplyEdits(filtered_edits) | |
| 141 | |
| 142 | |
| 143 if __name__ == '__main__': | |
| 144 sys.exit(main(sys.argv[1:])) | |
| OLD | NEW |