#!/usr/bin/env vpython3
# Copyright 2015 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Applies fixits generated by clang. clang's -Xclang -fixit-recompile flag
# automatically applies fixits and recompiles the result, but this does not work
# well with parallel clang invocations.
#
# 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
from __future__ import print_function
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 hack to sort fixits in line order but
# reverse column order. Applying the fixits in reverse order makes things
# simpler, since column 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.items():
v.sort()
with open(os.path.join(args.p, k), mode='r+', encoding='utf-8') as f:
lines = f.readlines()
last_fixit = None
line_offset = 0
for fixit in v:
if fixit == last_fixit:
continue
last_fixit = fixit
# The line/column numbers emitted in fixit hints start at 1, so offset
# is appropriately. Also apply unary `-` to all column numbers to
# reverse the hack above.
prefix = lines[fixit.start_line + line_offset - 1][:-fixit.start_col -
1]
suffix = lines[fixit.end_line + line_offset - 1][-fixit.end_col - 1:]
lines[fixit.start_line + line_offset - 1] = prefix + fixit.text + suffix
del lines[fixit.start_line + line_offset + 1 - 1:fixit.end_line +
line_offset + 1 - 1]
line_offset -= fixit.end_line - fixit.start_line
f.seek(0)
f.truncate()
f.writelines(lines)
if __name__ == '__main__':
sys.exit(main())