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