Home | History | Annotate | Download | only in scripts
      1 #!/usr/bin/env python
      2 # Copyright 2015 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.
      5 #
      6 # Script to apply fixits generated by clang. This is to work around the fact
      7 # that clang's -Xclang -fixit-recompile flag, which automatically applies fixits
      8 # and recompiles, doesn't work well with parallel invocations of clang.
      9 #
     10 # Usage:
     11 # 1. Enable parseable fixits and disable warnings as errors. Instructions for
     12 #    doing this vary based on the build environment, but for GN, warnings as
     13 #    errors can be disabled by setting treat_warnings_as_errors = false
     14 #    Enabling parseable fixits requires editing build/config/compiler/BUILD.gn
     15 #    and adding `-fdiagnostics-parseable-fixits` to cflags.
     16 # 2. Build everything and capture the output:
     17 #      ninja -C <build_directory> &> generated-fixits
     18 # 3. Apply the fixits with this script:
     19 #      python apply_fixits.py[ <build_directory>] < generated-fixits
     20 #    <build_directory> is optional and only required if your build directory is
     21 #    a non-standard location.
     22 
     23 import argparse
     24 import collections
     25 import fileinput
     26 import os
     27 import re
     28 import sys
     29 
     30 # fix-it:"../../base/threading/sequenced_worker_pool.h":{341:3-341:11}:""
     31 # Note that the file path is relative to the build directory.
     32 _FIXIT_RE = re.compile(r'^fix-it:"(?P<file>.+?)":'
     33                        r'{(?P<start_line>\d+?):(?P<start_col>\d+?)-'
     34                        r'(?P<end_line>\d+?):(?P<end_col>\d+?)}:'
     35                        r'"(?P<text>.*?)"$')
     36 
     37 FixIt = collections.namedtuple(
     38     'FixIt', ('start_line', 'start_col', 'end_line', 'end_col', 'text'))
     39 
     40 
     41 def main():
     42   parser = argparse.ArgumentParser()
     43   parser.add_argument(
     44       'build_directory',
     45       nargs='?',
     46       default='out/Debug',
     47       help='path to the build directory to complete relative paths in fixits')
     48   args = parser.parse_args()
     49 
     50   fixits = collections.defaultdict(list)
     51   for line in fileinput.input(['-']):
     52     if not line.startswith('fix-it:'):
     53       continue
     54     m = _FIXIT_RE.match(line)
     55     if not m:
     56       continue
     57     # The negative line numbers are a cheap hack so we can sort things in line
     58     # order but reverse column order. Applying the fixits in reverse order makes
     59     # things simpler, since offsets won't have to be adjusted as the text is
     60     # changed.
     61     fixits[m.group('file')].append(FixIt(
     62         int(m.group('start_line')), -int(m.group('start_col')), int(m.group(
     63             'end_line')), -int(m.group('end_col')), m.group('text')))
     64   for k, v in fixits.iteritems():
     65     v.sort()
     66     with open(os.path.join(args.build_directory, k), 'rb+') as f:
     67       lines = f.readlines()
     68       last_fixit = None
     69       for fixit in v:
     70         if fixit.start_line != fixit.end_line:
     71           print 'error: multiline fixits not supported! file: %s, fixit: %s' % (
     72               k, fixit)
     73           sys.exit(1)
     74         if fixit == last_fixit:
     75           continue
     76         last_fixit = fixit
     77         # The line/column numbers emitted in fixit hints start at 1, so offset
     78         # is appropriately.
     79         line = lines[fixit.start_line - 1]
     80         lines[fixit.start_line - 1] = (line[:-fixit.start_col - 1] + fixit.text
     81                                        + line[-fixit.end_col - 1:])
     82       f.seek(0)
     83       f.truncate()
     84       f.writelines(lines)
     85 
     86 
     87 if __name__ == '__main__':
     88   sys.exit(main())
     89