| #!/usr/bin/env python3 |
| # Copyright 2017 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Fix header files missing in GN. |
| |
| This script takes the missing header files from check_gn_headers.py, and |
| try to fix them by adding them to the GN files. |
| Manual cleaning up is likely required afterwards. |
| """ |
| |
| |
| import argparse |
| import os |
| import re |
| import subprocess |
| import sys |
| |
| |
| def GitGrep(pattern): |
| p = subprocess.Popen( |
| ['git', 'grep', '-En', pattern, '--', '*.gn', '*.gni'], |
| stdout=subprocess.PIPE) |
| out, _ = p.communicate() |
| return out, p.returncode |
| |
| |
| def ValidMatches(basename, cc, grep_lines): |
| """Filter out 'git grep' matches with header files already.""" |
| matches = [] |
| for line in grep_lines: |
| gnfile, linenr, contents = line.split(':') |
| linenr = int(linenr) |
| new = re.sub(cc, basename, contents) |
| lines = open(gnfile).read().splitlines() |
| assert contents in lines[linenr - 1] |
| # Skip if it's already there. It could be before or after the match. |
| if lines[linenr] == new: |
| continue |
| if lines[linenr - 2] == new: |
| continue |
| print(' ', gnfile, linenr, new) |
| matches.append((gnfile, linenr, new)) |
| return matches |
| |
| |
| def AddHeadersNextToCC(headers, skip_ambiguous=True): |
| """Add header files next to the corresponding .cc files in GN files. |
| |
| When skip_ambiguous is True, skip if multiple .cc files are found. |
| Returns unhandled headers. |
| |
| Manual cleaning up is likely required, especially if not skip_ambiguous. |
| """ |
| edits = {} |
| unhandled = [] |
| for filename in headers: |
| filename = filename.strip() |
| if not (filename.endswith('.h') or filename.endswith('.hh')): |
| continue |
| basename = os.path.basename(filename) |
| print(filename) |
| cc = r'\b' + os.path.splitext(basename)[0] + r'\.(cc|cpp|mm)\b' |
| out, returncode = GitGrep('(/|")' + cc + '"') |
| if returncode != 0 or not out: |
| unhandled.append(filename) |
| continue |
| |
| matches = ValidMatches(basename, cc, out.splitlines()) |
| |
| if len(matches) == 0: |
| continue |
| if len(matches) > 1: |
| print('\n[WARNING] Ambiguous matching for', filename) |
| for i in enumerate(matches, 1): |
| print('%d: %s' % (i[0], i[1])) |
| print() |
| if skip_ambiguous: |
| continue |
| |
| picked = raw_input('Pick the matches ("2,3" for multiple): ') |
| try: |
| matches = [matches[int(i) - 1] for i in picked.split(',')] |
| except (ValueError, IndexError): |
| continue |
| |
| for match in matches: |
| gnfile, linenr, new = match |
| print(' ', gnfile, linenr, new) |
| edits.setdefault(gnfile, {})[linenr] = new |
| |
| for gnfile in edits: |
| lines = open(gnfile).read().splitlines() |
| for l in sorted(edits[gnfile].keys(), reverse=True): |
| lines.insert(l, edits[gnfile][l]) |
| open(gnfile, 'w').write('\n'.join(lines) + '\n') |
| |
| return unhandled |
| |
| |
| def AddHeadersToSources(headers, skip_ambiguous=True): |
| """Add header files to the sources list in the first GN file. |
| |
| The target GN file is the first one up the parent directories. |
| This usually does the wrong thing for _test files if the test and the main |
| target are in the same .gn file. |
| When skip_ambiguous is True, skip if multiple sources arrays are found. |
| |
| "git cl format" afterwards is required. Manually cleaning up duplicated items |
| is likely required. |
| """ |
| for filename in headers: |
| filename = filename.strip() |
| print(filename) |
| dirname = os.path.dirname(filename) |
| while not os.path.exists(os.path.join(dirname, 'BUILD.gn')): |
| dirname = os.path.dirname(dirname) |
| rel = filename[len(dirname) + 1:] |
| gnfile = os.path.join(dirname, 'BUILD.gn') |
| |
| lines = open(gnfile).read().splitlines() |
| matched = [i for i, l in enumerate(lines) if ' sources = [' in l] |
| if skip_ambiguous and len(matched) > 1: |
| print('[WARNING] Multiple sources in', gnfile) |
| continue |
| |
| if len(matched) < 1: |
| continue |
| print(' ', gnfile, rel) |
| index = matched[0] |
| lines.insert(index + 1, '"%s",' % rel) |
| open(gnfile, 'w').write('\n'.join(lines) + '\n') |
| |
| |
| def RemoveHeader(headers, skip_ambiguous=True): |
| """Remove non-existing headers in GN files. |
| |
| When skip_ambiguous is True, skip if multiple matches are found. |
| """ |
| edits = {} |
| unhandled = [] |
| for filename in headers: |
| filename = filename.strip() |
| if not (filename.endswith('.h') or filename.endswith('.hh')): |
| continue |
| basename = os.path.basename(filename) |
| print(filename) |
| out, returncode = GitGrep('(/|")' + basename + '"') |
| if returncode != 0 or not out: |
| unhandled.append(filename) |
| print(' Not found') |
| continue |
| |
| grep_lines = out.splitlines() |
| matches = [] |
| for line in grep_lines: |
| gnfile, linenr, contents = line.split(':') |
| print(' ', gnfile, linenr, contents) |
| linenr = int(linenr) |
| lines = open(gnfile).read().splitlines() |
| assert contents in lines[linenr - 1] |
| matches.append((gnfile, linenr, contents)) |
| |
| if len(matches) == 0: |
| continue |
| if len(matches) > 1: |
| print('\n[WARNING] Ambiguous matching for', filename) |
| for i in enumerate(matches, 1): |
| print('%d: %s' % (i[0], i[1])) |
| print() |
| if skip_ambiguous: |
| continue |
| |
| picked = raw_input('Pick the matches ("2,3" for multiple): ') |
| try: |
| matches = [matches[int(i) - 1] for i in picked.split(',')] |
| except (ValueError, IndexError): |
| continue |
| |
| for match in matches: |
| gnfile, linenr, contents = match |
| print(' ', gnfile, linenr, contents) |
| edits.setdefault(gnfile, set()).add(linenr) |
| |
| for gnfile in edits: |
| lines = open(gnfile).read().splitlines() |
| for l in sorted(edits[gnfile], reverse=True): |
| lines.pop(l - 1) |
| open(gnfile, 'w').write('\n'.join(lines) + '\n') |
| |
| return unhandled |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument('input_file', help="missing or non-existing headers, " |
| "output of check_gn_headers.py") |
| parser.add_argument('--prefix', |
| help="only handle path name with this prefix") |
| parser.add_argument('--remove', action='store_true', |
| help="treat input_file as non-existing headers") |
| |
| args, _extras = parser.parse_known_args() |
| |
| headers = open(args.input_file).readlines() |
| |
| if args.prefix: |
| headers = [i for i in headers if i.startswith(args.prefix)] |
| |
| if args.remove: |
| RemoveHeader(headers, False) |
| else: |
| unhandled = AddHeadersNextToCC(headers) |
| AddHeadersToSources(unhandled) |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |