blob: aa98c73fb0909540665c0c957759337245100867 [file] [log] [blame]
wychenb6633582017-03-29 01:06:581#!/usr/bin/env python
2# Copyright 2017 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"""Fix header files missing in GN.
7
8This script takes the missing header files from check_gn_headers.py, and
9try to fix them by adding them to the GN files.
10Manual cleaning up is likely required afterwards.
11"""
12
13import argparse
14import os
15import re
16import subprocess
17import sys
18
19
wycheneb936d62017-04-01 05:42:5620def GitGrep(pattern):
21 p = subprocess.Popen(
22 ['git', 'grep', '-En', pattern, '--', '*.gn', '*.gni'],
23 stdout=subprocess.PIPE)
24 out, _ = p.communicate()
25 return out, p.returncode
26
27
28def ValidMatches(basename, cc, grep_lines):
29 """Filter out 'git grep' matches with header files already."""
30 matches = []
31 for line in grep_lines:
32 gnfile, linenr, contents = line.split(':')
33 linenr = int(linenr)
34 new = re.sub(cc, basename, contents)
35 lines = open(gnfile).read().splitlines()
36 assert contents in lines[linenr - 1]
37 # Skip if it's already there. It could be before or after the match.
38 if lines[linenr] == new:
39 continue
40 if lines[linenr - 2] == new:
41 continue
42 print ' ', gnfile, linenr, new
43 matches.append((gnfile, linenr, new))
44 return matches
45
46
wychenb6633582017-03-29 01:06:5847def AddHeadersNextToCC(headers, skip_ambiguous=True):
48 """Add header files next to the corresponding .cc files in GN files.
49
50 When skip_ambiguous is True, skip if multiple .cc files are found.
51 Returns unhandled headers.
52
53 Manual cleaning up is likely required, especially if not skip_ambiguous.
54 """
55 edits = {}
56 unhandled = []
57 for filename in headers:
58 filename = filename.strip()
59 if not (filename.endswith('.h') or filename.endswith('.hh')):
60 continue
61 basename = os.path.basename(filename)
62 print filename
63 cc = r'\b' + os.path.splitext(basename)[0] + r'\.(cc|cpp|mm)\b'
wycheneb936d62017-04-01 05:42:5664 out, returncode = GitGrep('(/|")' + cc + '"')
65 if returncode != 0 or not out:
wychenb6633582017-03-29 01:06:5866 unhandled.append(filename)
67 continue
68
wycheneb936d62017-04-01 05:42:5669 matches = ValidMatches(basename, cc, out.splitlines())
wychenb6633582017-03-29 01:06:5870
wycheneb936d62017-04-01 05:42:5671 if len(matches) == 0:
72 continue
73 if len(matches) > 1:
74 print '\n[WARNING] Ambiguous matching for', filename
75 for i in enumerate(matches, 1):
76 print '%d: %s' % (i[0], i[1])
77 print
78 if skip_ambiguous:
wychenb6633582017-03-29 01:06:5879 continue
wycheneb936d62017-04-01 05:42:5680
81 picked = raw_input('Pick the matches ("2,3" for multiple): ')
82 try:
83 matches = [matches[int(i) - 1] for i in picked.split(',')]
84 except (ValueError, IndexError):
wychenb6633582017-03-29 01:06:5885 continue
wycheneb936d62017-04-01 05:42:5686
87 for match in matches:
88 gnfile, linenr, new = match
wychenb6633582017-03-29 01:06:5889 print ' ', gnfile, linenr, new
90 edits.setdefault(gnfile, {})[linenr] = new
91
92 for gnfile in edits:
93 lines = open(gnfile).read().splitlines()
94 for l in sorted(edits[gnfile].keys(), reverse=True):
95 lines.insert(l, edits[gnfile][l])
96 open(gnfile, 'w').write('\n'.join(lines) + '\n')
97
98 return unhandled
99
100
101def AddHeadersToSources(headers, skip_ambiguous=True):
102 """Add header files to the sources list in the first GN file.
103
104 The target GN file is the first one up the parent directories.
105 This usually does the wrong thing for _test files if the test and the main
106 target are in the same .gn file.
107 When skip_ambiguous is True, skip if multiple sources arrays are found.
108
109 "git cl format" afterwards is required. Manually cleaning up duplicated items
110 is likely required.
111 """
112 for filename in headers:
113 filename = filename.strip()
114 print filename
115 dirname = os.path.dirname(filename)
116 while not os.path.exists(os.path.join(dirname, 'BUILD.gn')):
117 dirname = os.path.dirname(dirname)
118 rel = filename[len(dirname) + 1:]
119 gnfile = os.path.join(dirname, 'BUILD.gn')
120
121 lines = open(gnfile).read().splitlines()
122 matched = [i for i, l in enumerate(lines) if ' sources = [' in l]
123 if skip_ambiguous and len(matched) > 1:
124 print '[WARNING] Multiple sources in', gnfile
125 continue
126
127 if len(matched) < 1:
128 continue
129 print ' ', gnfile, rel
130 index = matched[0]
131 lines.insert(index + 1, '"%s",' % rel)
132 open(gnfile, 'w').write('\n'.join(lines) + '\n')
133
134
135def main():
136 parser = argparse.ArgumentParser()
137 parser.add_argument('input_file',
138 help="missing headers, output of check_gn_headers.py")
139 parser.add_argument('--prefix',
140 help="only handle path name with this prefix")
141
142 args, _extras = parser.parse_known_args()
143
144 headers = open(args.input_file).readlines()
145
146 if args.prefix:
147 headers = [i for i in headers if i.startswith(args.prefix)]
148
149 unhandled = AddHeadersNextToCC(headers)
150 AddHeadersToSources(unhandled)
151
152
153if __name__ == '__main__':
154 sys.exit(main())