| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Wrapper script to help run clang tools across Chromium code. | 6 """Wrapper script to help run clang tools across Chromium code. |
| 7 | 7 |
| 8 How to use this tool: | 8 How to use this tool: |
| 9 If you want to run the tool across all Chromium code: | 9 If you want to run the tool across all Chromium code: |
| 10 run_tool.py <tool> <path/to/compiledb> | 10 run_tool.py <tool> <path/to/compiledb> |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 47 Edit = collections.namedtuple( | 47 Edit = collections.namedtuple( |
| 48 'Edit', ('edit_type', 'offset', 'length', 'replacement')) | 48 'Edit', ('edit_type', 'offset', 'length', 'replacement')) |
| 49 | 49 |
| 50 | 50 |
| 51 def _GetFilesFromGit(paths = None): | 51 def _GetFilesFromGit(paths = None): |
| 52 """Gets the list of files in the git repository. | 52 """Gets the list of files in the git repository. |
| 53 | 53 |
| 54 Args: | 54 Args: |
| 55 paths: Prefix filter for the returned paths. May contain multiple entries. | 55 paths: Prefix filter for the returned paths. May contain multiple entries. |
| 56 """ | 56 """ |
| 57 args = ['git', 'ls-files'] | 57 args = [] |
| 58 if sys.platform == 'win32': |
| 59 args.append('git.bat') |
| 60 else: |
| 61 args.append('git') |
| 62 args.append('ls-files') |
| 58 if paths: | 63 if paths: |
| 59 args.extend(paths) | 64 args.extend(paths) |
| 60 command = subprocess.Popen(args, stdout=subprocess.PIPE) | 65 command = subprocess.Popen(args, stdout=subprocess.PIPE) |
| 61 output, _ = command.communicate() | 66 output, _ = command.communicate() |
| 62 return output.splitlines() | 67 return [os.path.realpath(p) for p in output.splitlines()] |
| 63 | 68 |
| 64 | 69 |
| 65 def _ExtractEditsFromStdout(build_directory, stdout): | 70 def _ExtractEditsFromStdout(build_directory, stdout): |
| 66 """Extracts generated list of edits from the tool's stdout. | 71 """Extracts generated list of edits from the tool's stdout. |
| 67 | 72 |
| 68 The expected format is documented at the top of this file. | 73 The expected format is documented at the top of this file. |
| 69 | 74 |
| 70 Args: | 75 Args: |
| 71 build_directory: Directory that contains the compile database. Used to | 76 build_directory: Directory that contains the compile database. Used to |
| 72 normalize the filenames. | 77 normalize the filenames. |
| 73 stdout: The stdout from running the clang tool. | 78 stdout: The stdout from running the clang tool. |
| 74 | 79 |
| 75 Returns: | 80 Returns: |
| 76 A dictionary mapping filenames to the associated edits. | 81 A dictionary mapping filenames to the associated edits. |
| 77 """ | 82 """ |
| 78 lines = stdout.splitlines() | 83 lines = stdout.splitlines() |
| 79 start_index = lines.index('==== BEGIN EDITS ====') | 84 start_index = lines.index('==== BEGIN EDITS ====') |
| 80 end_index = lines.index('==== END EDITS ====') | 85 end_index = lines.index('==== END EDITS ====') |
| 81 edits = collections.defaultdict(list) | 86 edits = collections.defaultdict(list) |
| 82 for line in lines[start_index + 1:end_index]: | 87 for line in lines[start_index + 1:end_index]: |
| 83 try: | 88 try: |
| 84 edit_type, path, offset, length, replacement = line.split(':', 4) | 89 edit_type, path, offset, length, replacement = line.split(':::', 4) |
| 85 replacement = replacement.replace("\0", "\n"); | 90 replacement = replacement.replace("\0", "\n"); |
| 86 # Normalize the file path emitted by the clang tool to be relative to the | 91 # Normalize the file path emitted by the clang tool. |
| 87 # current working directory. | 92 path = os.path.realpath(os.path.join(build_directory, path)) |
| 88 path = os.path.relpath(os.path.join(build_directory, path)) | |
| 89 edits[path].append(Edit(edit_type, int(offset), int(length), replacement)) | 93 edits[path].append(Edit(edit_type, int(offset), int(length), replacement)) |
| 90 except ValueError: | 94 except ValueError: |
| 91 print 'Unable to parse edit: %s' % line | 95 print 'Unable to parse edit: %s' % line |
| 92 return edits | 96 return edits |
| 93 | 97 |
| 94 | 98 |
| 95 def _ExecuteTool(toolname, build_directory, filename): | 99 def _ExecuteTool(toolname, build_directory, filename): |
| 96 """Executes the tool. | 100 """Executes the tool. |
| 97 | 101 |
| 98 This is defined outside the class so it can be pickled for the multiprocessing | 102 This is defined outside the class so it can be pickled for the multiprocessing |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 133 Args: | 137 Args: |
| 134 toolname: Path to the tool to execute. | 138 toolname: Path to the tool to execute. |
| 135 build_directory: Directory that contains the compile database. | 139 build_directory: Directory that contains the compile database. |
| 136 filenames: The files to run the tool over. | 140 filenames: The files to run the tool over. |
| 137 """ | 141 """ |
| 138 self.__toolname = toolname | 142 self.__toolname = toolname |
| 139 self.__build_directory = build_directory | 143 self.__build_directory = build_directory |
| 140 self.__filenames = filenames | 144 self.__filenames = filenames |
| 141 self.__success_count = 0 | 145 self.__success_count = 0 |
| 142 self.__failed_count = 0 | 146 self.__failed_count = 0 |
| 147 self.__edit_count = 0 |
| 143 self.__edits = collections.defaultdict(list) | 148 self.__edits = collections.defaultdict(list) |
| 144 | 149 |
| 145 @property | 150 @property |
| 146 def edits(self): | 151 def edits(self): |
| 147 return self.__edits | 152 return self.__edits |
| 148 | 153 |
| 149 @property | 154 @property |
| 150 def failed_count(self): | 155 def failed_count(self): |
| 151 return self.__failed_count | 156 return self.__failed_count |
| 152 | 157 |
| (...skipping 12 matching lines...) Expand all Loading... |
| 165 def __ProcessResult(self, result): | 170 def __ProcessResult(self, result): |
| 166 """Handles result processing. | 171 """Handles result processing. |
| 167 | 172 |
| 168 Args: | 173 Args: |
| 169 result: The result dictionary returned by _ExecuteTool. | 174 result: The result dictionary returned by _ExecuteTool. |
| 170 """ | 175 """ |
| 171 if result['status']: | 176 if result['status']: |
| 172 self.__success_count += 1 | 177 self.__success_count += 1 |
| 173 for k, v in result['edits'].iteritems(): | 178 for k, v in result['edits'].iteritems(): |
| 174 self.__edits[k].extend(v) | 179 self.__edits[k].extend(v) |
| 180 self.__edit_count += len(v) |
| 175 else: | 181 else: |
| 176 self.__failed_count += 1 | 182 self.__failed_count += 1 |
| 177 sys.stdout.write('\nFailed to process %s\n' % result['filename']) | 183 sys.stdout.write('\nFailed to process %s\n' % result['filename']) |
| 178 sys.stdout.write(result['stderr']) | 184 sys.stdout.write(result['stderr']) |
| 179 sys.stdout.write('\n') | 185 sys.stdout.write('\n') |
| 180 percentage = ( | 186 percentage = ( |
| 181 float(self.__success_count + self.__failed_count) / | 187 float(self.__success_count + self.__failed_count) / |
| 182 len(self.__filenames)) * 100 | 188 len(self.__filenames)) * 100 |
| 183 sys.stdout.write('Succeeded: %d, Failed: %d [%.2f%%]\r' % ( | 189 sys.stdout.write('Succeeded: %d, Failed: %d, Edits: %d [%.2f%%]\r' % ( |
| 184 self.__success_count, self.__failed_count, percentage)) | 190 self.__success_count, self.__failed_count, self.__edit_count, |
| 191 percentage)) |
| 185 sys.stdout.flush() | 192 sys.stdout.flush() |
| 186 | 193 |
| 187 | 194 |
| 188 def _ApplyEdits(edits, clang_format_diff_path): | 195 def _ApplyEdits(edits, clang_format_diff_path): |
| 189 """Apply the generated edits. | 196 """Apply the generated edits. |
| 190 | 197 |
| 191 Args: | 198 Args: |
| 192 edits: A dict mapping filenames to Edit instances that apply to that file. | 199 edits: A dict mapping filenames to Edit instances that apply to that file. |
| 193 clang_format_diff_path: Path to the clang-format-diff.py helper to help | 200 clang_format_diff_path: Path to the clang-format-diff.py helper to help |
| 194 automatically reformat diffs to avoid style violations. Pass None if the | 201 automatically reformat diffs to avoid style violations. Pass None if the |
| (...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 276 print ' <clang tool> is the clang tool that should be run.' | 283 print ' <clang tool> is the clang tool that should be run.' |
| 277 print ' <compile db> is the directory that contains the compile database' | 284 print ' <compile db> is the directory that contains the compile database' |
| 278 print ' <path 1> <path2> ... can be used to filter what files are edited' | 285 print ' <path 1> <path2> ... can be used to filter what files are edited' |
| 279 return 1 | 286 return 1 |
| 280 | 287 |
| 281 clang_format_diff_path = os.path.join( | 288 clang_format_diff_path = os.path.join( |
| 282 os.path.dirname(os.path.realpath(__file__)), | 289 os.path.dirname(os.path.realpath(__file__)), |
| 283 '../../../third_party/llvm/tools/clang/tools/clang-format', | 290 '../../../third_party/llvm/tools/clang/tools/clang-format', |
| 284 'clang-format-diff.py') | 291 'clang-format-diff.py') |
| 285 # TODO(dcheng): Allow this to be controlled with a flag as well. | 292 # TODO(dcheng): Allow this to be controlled with a flag as well. |
| 286 if not os.path.isfile(clang_format_diff_path): | 293 # TODO(dcheng): Shell escaping of args to git diff to clang-format is broken |
| 294 # on Windows. |
| 295 if not os.path.isfile(clang_format_diff_path) or sys.platform == 'win32': |
| 287 clang_format_diff_path = None | 296 clang_format_diff_path = None |
| 288 | 297 |
| 289 filenames = frozenset(_GetFilesFromGit(argv[2:])) | 298 filenames = frozenset(_GetFilesFromGit(argv[2:])) |
| 290 # Filter out files that aren't C/C++/Obj-C/Obj-C++. | 299 # Filter out files that aren't C/C++/Obj-C/Obj-C++. |
| 291 extensions = frozenset(('.c', '.cc', '.m', '.mm')) | 300 extensions = frozenset(('.c', '.cc', '.m', '.mm')) |
| 292 dispatcher = _CompilerDispatcher(argv[0], argv[1], | 301 dispatcher = _CompilerDispatcher(argv[0], argv[1], |
| 293 [f for f in filenames | 302 [f for f in filenames |
| 294 if os.path.splitext(f)[1] in extensions]) | 303 if os.path.splitext(f)[1] in extensions]) |
| 295 dispatcher.Run() | 304 dispatcher.Run() |
| 296 # Filter out edits to files that aren't in the git repository, since it's not | 305 # Filter out edits to files that aren't in the git repository, since it's not |
| 297 # useful to modify files that aren't under source control--typically, these | 306 # useful to modify files that aren't under source control--typically, these |
| 298 # are generated files or files in a git submodule that's not part of Chromium. | 307 # are generated files or files in a git submodule that's not part of Chromium. |
| 299 _ApplyEdits({k : v for k, v in dispatcher.edits.iteritems() | 308 _ApplyEdits({k : v for k, v in dispatcher.edits.iteritems() |
| 300 if k in filenames}, | 309 if os.path.realpath(k) in filenames}, |
| 301 clang_format_diff_path) | 310 clang_format_diff_path) |
| 302 if dispatcher.failed_count != 0: | 311 if dispatcher.failed_count != 0: |
| 303 return 2 | 312 return 2 |
| 304 return 0 | 313 return 0 |
| 305 | 314 |
| 306 | 315 |
| 307 if __name__ == '__main__': | 316 if __name__ == '__main__': |
| 308 sys.exit(main(sys.argv[1:])) | 317 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |