1c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#!/usr/bin/env python
2c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)# Copyright (c) 2013 The Chromium Authors. All rights reserved.
3c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
4c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)# found in the LICENSE file.
5c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
6c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)"""Wrapper script to help run clang tools across Chromium code.
7c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
8c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)How to use this tool:
9c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)If you want to run the tool across all Chromium code:
10c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)run_tool.py <tool> <path/to/compiledb>
11c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
12c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)If you only want to run the tool across just chrome/browser and content/browser:
13c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)run_tool.py <tool> <path/to/compiledb> chrome/browser content/browser
14c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
15c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)Please see https://code.google.com/p/chromium/wiki/ClangToolRefactoring for more
16c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)information, which documents the entire automated refactoring flow in Chromium.
17c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
18c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)Why use this tool:
19c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)The clang tool implementation doesn't take advantage of multiple cores, and if
20c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)it fails mysteriously in the middle, all the generated replacements will be
21c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)lost.
22c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
23c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)Unfortunately, if the work is simply sharded across multiple cores by running
24c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)multiple RefactoringTools, problems arise when they attempt to rewrite a file at
25c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)the same time. To work around that, clang tools that are run using this tool
26c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)should output edits to stdout in the following format:
27c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
28c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)==== BEGIN EDITS ====
29c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)r:<file path>:<offset>:<length>:<replacement text>
30c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)r:<file path>:<offset>:<length>:<replacement text>
31c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)...etc...
32c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)==== END EDITS ====
33c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
34c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)Any generated edits are applied once the clang tool has finished running
35c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)across Chromium, regardless of whether some instances failed or not.
36c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)"""
37c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
38c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import collections
39c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import functools
40c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import multiprocessing
41c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import os.path
426e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)import pipes
43c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import subprocess
44c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import sys
45c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
46c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
47c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)Edit = collections.namedtuple(
48c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    'Edit', ('edit_type', 'offset', 'length', 'replacement'))
49c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
50c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
51c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)def _GetFilesFromGit(paths = None):
52c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  """Gets the list of files in the git repository.
53c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
54c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  Args:
55c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    paths: Prefix filter for the returned paths. May contain multiple entries.
56c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  """
57c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  args = ['git', 'ls-files']
58c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  if paths:
59c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    args.extend(paths)
60c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  command = subprocess.Popen(args, stdout=subprocess.PIPE)
61c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  output, _ = command.communicate()
62c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  return output.splitlines()
63c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
64c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
65c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)def _ExtractEditsFromStdout(build_directory, stdout):
66c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  """Extracts generated list of edits from the tool's stdout.
67c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
68c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  The expected format is documented at the top of this file.
69c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
70c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  Args:
71c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    build_directory: Directory that contains the compile database. Used to
72c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      normalize the filenames.
73c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    stdout: The stdout from running the clang tool.
74c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
75c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  Returns:
76c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    A dictionary mapping filenames to the associated edits.
77c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  """
78c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  lines = stdout.splitlines()
79c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  start_index = lines.index('==== BEGIN EDITS ====')
80c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  end_index = lines.index('==== END EDITS ====')
81c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  edits = collections.defaultdict(list)
82c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  for line in lines[start_index + 1:end_index]:
83c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    try:
84c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      edit_type, path, offset, length, replacement = line.split(':', 4)
856e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      replacement = replacement.replace("\0", "\n");
86c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      # Normalize the file path emitted by the clang tool to be relative to the
87c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      # current working directory.
88c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      path = os.path.relpath(os.path.join(build_directory, path))
89c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      edits[path].append(Edit(edit_type, int(offset), int(length), replacement))
90c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    except ValueError:
91c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      print 'Unable to parse edit: %s' % line
92c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  return edits
93c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
94c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
95c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)def _ExecuteTool(toolname, build_directory, filename):
96c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  """Executes the tool.
97c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
98c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  This is defined outside the class so it can be pickled for the multiprocessing
99c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  module.
100c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
101c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  Args:
102c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    toolname: Path to the tool to execute.
103c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    build_directory: Directory that contains the compile database.
104c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    filename: The file to run the tool over.
105c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
106c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  Returns:
107c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    A dictionary that must contain the key "status" and a boolean value
108c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    associated with it.
109c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
110c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    If status is True, then the generated edits are stored with the key "edits"
111c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    in the dictionary.
112c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
113c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    Otherwise, the filename and the output from stderr are associated with the
114c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    keys "filename" and "stderr" respectively.
115c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  """
116c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  command = subprocess.Popen((toolname, '-p', build_directory, filename),
117c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                             stdout=subprocess.PIPE,
118c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                             stderr=subprocess.PIPE)
119c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  stdout, stderr = command.communicate()
120c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  if command.returncode != 0:
121c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    return {'status': False, 'filename': filename, 'stderr': stderr}
122c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  else:
123c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    return {'status': True,
124c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)            'edits': _ExtractEditsFromStdout(build_directory, stdout)}
125c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
126c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
127c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)class _CompilerDispatcher(object):
128c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  """Multiprocessing controller for running clang tools in parallel."""
129c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
130c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  def __init__(self, toolname, build_directory, filenames):
131c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    """Initializer method.
132c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
133c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    Args:
134c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      toolname: Path to the tool to execute.
135c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      build_directory: Directory that contains the compile database.
136c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      filenames: The files to run the tool over.
137c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    """
138c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    self.__toolname = toolname
139c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    self.__build_directory = build_directory
140c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    self.__filenames = filenames
141c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    self.__success_count = 0
142c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    self.__failed_count = 0
143c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    self.__edits = collections.defaultdict(list)
144c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
145c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  @property
146c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  def edits(self):
147c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    return self.__edits
148c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
149c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  @property
150c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  def failed_count(self):
151c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    return self.__failed_count
152c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
153c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  def Run(self):
154c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    """Does the grunt work."""
155c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    pool = multiprocessing.Pool()
156c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    result_iterator = pool.imap_unordered(
157c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        functools.partial(_ExecuteTool, self.__toolname,
158c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                          self.__build_directory),
159c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        self.__filenames)
160c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    for result in result_iterator:
161c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      self.__ProcessResult(result)
162c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    sys.stdout.write('\n')
163c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    sys.stdout.flush()
164c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
165c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  def __ProcessResult(self, result):
166c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    """Handles result processing.
167c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
168c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    Args:
169c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      result: The result dictionary returned by _ExecuteTool.
170c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    """
171c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if result['status']:
172c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      self.__success_count += 1
173c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      for k, v in result['edits'].iteritems():
174c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        self.__edits[k].extend(v)
175c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    else:
176c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      self.__failed_count += 1
177c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      sys.stdout.write('\nFailed to process %s\n' % result['filename'])
178c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      sys.stdout.write(result['stderr'])
179c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      sys.stdout.write('\n')
180c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    percentage = (
181c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        float(self.__success_count + self.__failed_count) /
182c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        len(self.__filenames)) * 100
183c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    sys.stdout.write('Succeeded: %d, Failed: %d [%.2f%%]\r' % (
184c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        self.__success_count, self.__failed_count, percentage))
185c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    sys.stdout.flush()
186c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
187c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
188c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)def _ApplyEdits(edits, clang_format_diff_path):
189c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  """Apply the generated edits.
190c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
191c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  Args:
192c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    edits: A dict mapping filenames to Edit instances that apply to that file.
193c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    clang_format_diff_path: Path to the clang-format-diff.py helper to help
194c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      automatically reformat diffs to avoid style violations. Pass None if the
195c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      clang-format step should be skipped.
196c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  """
197c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  edit_count = 0
198c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  for k, v in edits.iteritems():
199c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # Sort the edits and iterate through them in reverse order. Sorting allows
200c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # duplicate edits to be quickly skipped, while reversing means that
201c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # subsequent edits don't need to have their offsets updated with each edit
202c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # applied.
203c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    v.sort()
204c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    last_edit = None
205c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    with open(k, 'rb+') as f:
206c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      contents = bytearray(f.read())
207c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      for edit in reversed(v):
208c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        if edit == last_edit:
209c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          continue
210c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        last_edit = edit
211c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        contents[edit.offset:edit.offset + edit.length] = edit.replacement
212c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        if not edit.replacement:
213c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          _ExtendDeletionIfElementIsInList(contents, edit.offset)
214c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        edit_count += 1
215c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      f.seek(0)
216c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      f.truncate()
217c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      f.write(contents)
218c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if clang_format_diff_path:
2196e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      # TODO(dcheng): python3.3 exposes this publicly as shlex.quote, but Chrome
2206e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      # uses python2.7. Use the deprecated interface until Chrome uses a newer
2216e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      # Python.
2225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      if subprocess.call('git diff -U0 %s | python %s -i -p1 -style=file ' % (
2236e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)          pipes.quote(k), clang_format_diff_path), shell=True) != 0:
224c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        print 'clang-format failed for %s' % k
225c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  print 'Applied %d edits to %d files' % (edit_count, len(edits))
226c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
227c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
228c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)_WHITESPACE_BYTES = frozenset((ord('\t'), ord('\n'), ord('\r'), ord(' ')))
229c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
230c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
231c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)def _ExtendDeletionIfElementIsInList(contents, offset):
232c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  """Extends the range of a deletion if the deleted element was part of a list.
233c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
234c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  This rewriter helper makes it easy for refactoring tools to remove elements
235c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  from a list. Even if a matcher callback knows that it is removing an element
236c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  from a list, it may not have enough information to accurately remove the list
237c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  element; for example, another matcher callback may end up removing an adjacent
238c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  list element, or all the list elements may end up being removed.
239c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
240c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  With this helper, refactoring tools can simply remove the list element and not
241c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  worry about having to include the comma in the replacement.
242c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
243c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  Args:
244c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    contents: A bytearray with the deletion already applied.
245c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    offset: The offset in the bytearray where the deleted range used to be.
246c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  """
247c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  char_before = char_after = None
248c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  left_trim_count = 0
249c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  for byte in reversed(contents[:offset]):
250c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    left_trim_count += 1
251c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if byte in _WHITESPACE_BYTES:
252c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      continue
253c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if byte in (ord(','), ord(':'), ord('('), ord('{')):
254c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      char_before = chr(byte)
255c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    break
256c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
257c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  right_trim_count = 0
258c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  for byte in contents[offset:]:
259c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    right_trim_count += 1
260c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if byte in _WHITESPACE_BYTES:
261c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      continue
262c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if byte == ord(','):
263c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      char_after = chr(byte)
264c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    break
265c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
266c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  if char_before:
267c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if char_after:
268c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      del contents[offset:offset + right_trim_count]
269c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    elif char_before in (',', ':'):
270c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      del contents[offset - left_trim_count:offset]
271c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
272c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
273c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)def main(argv):
274c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  if len(argv) < 2:
275c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    print 'Usage: run_tool.py <clang tool> <compile DB> <path 1> <path 2> ...'
276c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    print '  <clang tool> is the clang tool that should be run.'
277c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    print '  <compile db> is the directory that contains the compile database'
278c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    print '  <path 1> <path2> ... can be used to filter what files are edited'
279c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    return 1
280c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
281c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  clang_format_diff_path = os.path.join(
282c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      os.path.dirname(os.path.realpath(__file__)),
283c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      '../../../third_party/llvm/tools/clang/tools/clang-format',
284c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      'clang-format-diff.py')
285c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  # TODO(dcheng): Allow this to be controlled with a flag as well.
286c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  if not os.path.isfile(clang_format_diff_path):
287c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    clang_format_diff_path = None
288c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
289c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  filenames = frozenset(_GetFilesFromGit(argv[2:]))
290c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  # Filter out files that aren't C/C++/Obj-C/Obj-C++.
291c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  extensions = frozenset(('.c', '.cc', '.m', '.mm'))
292c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  dispatcher = _CompilerDispatcher(argv[0], argv[1],
293c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                                   [f for f in filenames
294c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                                    if os.path.splitext(f)[1] in extensions])
295c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  dispatcher.Run()
296c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  # Filter out edits to files that aren't in the git repository, since it's not
297c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  # useful to modify files that aren't under source control--typically, these
298c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  # are generated files or files in a git submodule that's not part of Chromium.
299c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  _ApplyEdits({k : v for k, v in dispatcher.edits.iteritems()
300c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                    if k in filenames},
301c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)              clang_format_diff_path)
302c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  if dispatcher.failed_count != 0:
303c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    return 2
304c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  return 0
305c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
306c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
307c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)if __name__ == '__main__':
308c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  sys.exit(main(sys.argv[1:]))
309