2 # Copyright 2017 The Chromium Authors
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """Fix header files missing in GN.
8 This script takes the missing header files from check_gn_headers.py, and
9 try to fix them by adding them to the GN files.
10 Manual cleaning up is likely required afterwards.
23 ['git', 'grep', '-En', pattern, '--', '*.gn', '*.gni'],
24 stdout=subprocess.PIPE)
25 out, _ = p.communicate()
26 return out, p.returncode
29 def ValidMatches(basename, cc, grep_lines):
30 """Filter out 'git grep' matches with header files already."""
32 for line in grep_lines:
33 gnfile, linenr, contents = line.split(':')
35 new = re.sub(cc, basename, contents)
36 lines = open(gnfile).read().splitlines()
37 assert contents in lines[linenr - 1]
38 # Skip if it's already there. It could be before or after the match.
39 if lines[linenr] == new:
41 if lines[linenr - 2] == new:
43 print(' ', gnfile, linenr, new)
44 matches.append((gnfile, linenr, new))
48 def AddHeadersNextToCC(headers, skip_ambiguous=True):
49 """Add header files next to the corresponding .cc files in GN files.
51 When skip_ambiguous is True, skip if multiple .cc files are found.
52 Returns unhandled headers.
54 Manual cleaning up is likely required, especially if not skip_ambiguous.
58 for filename in headers:
59 filename = filename.strip()
60 if not (filename.endswith('.h') or filename.endswith('.hh')):
62 basename = os.path.basename(filename)
64 cc = r'\b' + os.path.splitext(basename)[0] + r'\.(cc|cpp|mm)\b'
65 out, returncode = GitGrep('(/|")' + cc + '"')
66 if returncode != 0 or not out:
67 unhandled.append(filename)
70 matches = ValidMatches(basename, cc, out.splitlines())
75 print('\n[WARNING] Ambiguous matching for', filename)
76 for i in enumerate(matches, 1):
77 print('%d: %s' % (i[0], i[1]))
82 picked = raw_input('Pick the matches ("2,3" for multiple): ')
84 matches = [matches[int(i) - 1] for i in picked.split(',')]
85 except (ValueError, IndexError):
89 gnfile, linenr, new = match
90 print(' ', gnfile, linenr, new)
91 edits.setdefault(gnfile, {})[linenr] = new
94 lines = open(gnfile).read().splitlines()
95 for l in sorted(edits[gnfile].keys(), reverse=True):
96 lines.insert(l, edits[gnfile][l])
97 open(gnfile, 'w').write('\n'.join(lines) + '\n')
102 def AddHeadersToSources(headers, skip_ambiguous=True):
103 """Add header files to the sources list in the first GN file.
105 The target GN file is the first one up the parent directories.
106 This usually does the wrong thing for _test files if the test and the main
107 target are in the same .gn file.
108 When skip_ambiguous is True, skip if multiple sources arrays are found.
110 "git cl format" afterwards is required. Manually cleaning up duplicated items
113 for filename in headers:
114 filename = filename.strip()
116 dirname = os.path.dirname(filename)
117 while not os.path.exists(os.path.join(dirname, 'BUILD.gn')):
118 dirname = os.path.dirname(dirname)
119 rel = filename[len(dirname) + 1:]
120 gnfile = os.path.join(dirname, 'BUILD.gn')
122 lines = open(gnfile).read().splitlines()
123 matched = [i for i, l in enumerate(lines) if ' sources = [' in l]
124 if skip_ambiguous and len(matched) > 1:
125 print('[WARNING] Multiple sources in', gnfile)
130 print(' ', gnfile, rel)
132 lines.insert(index + 1, '"%s",' % rel)
133 open(gnfile, 'w').write('\n'.join(lines) + '\n')
136 def RemoveHeader(headers, skip_ambiguous=True):
137 """Remove non-existing headers in GN files.
139 When skip_ambiguous is True, skip if multiple matches are found.
143 for filename in headers:
144 filename = filename.strip()
145 if not (filename.endswith('.h') or filename.endswith('.hh')):
147 basename = os.path.basename(filename)
149 out, returncode = GitGrep('(/|")' + basename + '"')
150 if returncode != 0 or not out:
151 unhandled.append(filename)
155 grep_lines = out.splitlines()
157 for line in grep_lines:
158 gnfile, linenr, contents = line.split(':')
159 print(' ', gnfile, linenr, contents)
161 lines = open(gnfile).read().splitlines()
162 assert contents in lines[linenr - 1]
163 matches.append((gnfile, linenr, contents))
165 if len(matches) == 0:
168 print('\n[WARNING] Ambiguous matching for', filename)
169 for i in enumerate(matches, 1):
170 print('%d: %s' % (i[0], i[1]))
175 picked = raw_input('Pick the matches ("2,3" for multiple): ')
177 matches = [matches[int(i) - 1] for i in picked.split(',')]
178 except (ValueError, IndexError):
181 for match in matches:
182 gnfile, linenr, contents = match
183 print(' ', gnfile, linenr, contents)
184 edits.setdefault(gnfile, set()).add(linenr)
187 lines = open(gnfile).read().splitlines()
188 for l in sorted(edits[gnfile], reverse=True):
190 open(gnfile, 'w').write('\n'.join(lines) + '\n')
196 parser = argparse.ArgumentParser()
197 parser.add_argument('input_file', help="missing or non-existing headers, "
198 "output of check_gn_headers.py")
199 parser.add_argument('--prefix',
200 help="only handle path name with this prefix")
201 parser.add_argument('--remove', action='store_true',
202 help="treat input_file as non-existing headers")
204 args, _extras = parser.parse_known_args()
206 headers = open(args.input_file).readlines()
209 headers = [i for i in headers if i.startswith(args.prefix)]
212 RemoveHeader(headers, False)
214 unhandled = AddHeadersNextToCC(headers)
215 AddHeadersToSources(unhandled)
218 if __name__ == '__main__':