|  | #!/usr/bin/env python | 
|  | # Copyright 2017 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. | 
|  |  | 
|  | """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()) |