| #!/usr/bin/env python |
| # Copyright 2015 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. |
| # |
| # Script to apply fixits generated by clang. This is to work around the fact |
| # that clang's -Xclang -fixit-recompile flag, which automatically applies fixits |
| # and recompiles, doesn't work well with parallel invocations of clang. |
| # |
| # Usage: |
| # 1. Enable parseable fixits and disable warnings as errors. Instructions for |
| # doing this vary based on the build environment, but for GN, warnings as |
| # errors can be disabled by setting treat_warnings_as_errors = false |
| # Enabling parseable fixits requires editing build/config/compiler/BUILD.gn |
| # and adding `-fdiagnostics-parseable-fixits` to cflags. |
| # 2. Build everything and capture the output: |
| # ninja -C <build_directory> &> generated-fixits |
| # 3. Apply the fixits with this script: |
| # python apply_fixits.py[ <build_directory>] < generated-fixits |
| # <build_directory> is optional and only required if your build directory is |
| # a non-standard location. |
| |
| import argparse |
| import collections |
| import fileinput |
| import os |
| import re |
| import sys |
| |
| # fix-it:"../../base/threading/sequenced_worker_pool.h":{341:3-341:11}:"" |
| # Note that the file path is relative to the build directory. |
| _FIXIT_RE = re.compile(r'^fix-it:"(?P<file>.+?)":' |
| r'{(?P<start_line>\d+?):(?P<start_col>\d+?)-' |
| r'(?P<end_line>\d+?):(?P<end_col>\d+?)}:' |
| r'"(?P<text>.*?)"$') |
| |
| FixIt = collections.namedtuple( |
| 'FixIt', ('start_line', 'start_col', 'end_line', 'end_col', 'text')) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| 'build_directory', |
| nargs='?', |
| default='out/Debug', |
| help='path to the build directory to complete relative paths in fixits') |
| args = parser.parse_args() |
| |
| fixits = collections.defaultdict(list) |
| for line in fileinput.input(['-']): |
| if not line.startswith('fix-it:'): |
| continue |
| m = _FIXIT_RE.match(line) |
| if not m: |
| continue |
| # The negative line numbers are a cheap hack so we can sort things in line |
| # order but reverse column order. Applying the fixits in reverse order makes |
| # things simpler, since offsets won't have to be adjusted as the text is |
| # changed. |
| fixits[m.group('file')].append(FixIt( |
| int(m.group('start_line')), -int(m.group('start_col')), int(m.group( |
| 'end_line')), -int(m.group('end_col')), m.group('text'))) |
| for k, v in fixits.iteritems(): |
| v.sort() |
| with open(os.path.join(args.build_directory, k), 'rb+') as f: |
| lines = f.readlines() |
| last_fixit = None |
| for fixit in v: |
| if fixit.start_line != fixit.end_line: |
| print 'error: multiline fixits not supported! file: %s, fixit: %s' % ( |
| k, fixit) |
| sys.exit(1) |
| if fixit == last_fixit: |
| continue |
| last_fixit = fixit |
| # The line/column numbers emitted in fixit hints start at 1, so offset |
| # is appropriately. |
| line = lines[fixit.start_line - 1] |
| lines[fixit.start_line - 1] = (line[:-fixit.start_col - 1] + fixit.text |
| + line[-fixit.end_col - 1:]) |
| f.seek(0) |
| f.truncate() |
| f.writelines(lines) |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |