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 23import argparse 24import collections 25import fileinput 26import os 27import re 28import 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 37FixIt = collections.namedtuple( 38 'FixIt', ('start_line', 'start_col', 'end_line', 'end_col', 'text')) 39 40 41def 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 87if __name__ == '__main__': 88 sys.exit(main()) 89