#!/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())