| #!/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 -p <build_directory> < generated-fixits | |
| 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( | |
| '-p', | |
| required=True, | |
| 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.p, 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()) |