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.
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.
22 ['git', 'grep', '-En', pattern, '--', '*.gn', '*.gni'],
23 stdout=subprocess.PIPE)
24 out, _ = p.communicate()
25 return out, p.returncode
28 def ValidMatches(basename, cc, grep_lines):
29 """Filter out 'git grep' matches with header files already."""
31 for line in grep_lines:
32 gnfile, linenr, contents = line.split(':')
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:
40 if lines[linenr - 2] == new:
42 print ' ', gnfile, linenr, new
43 matches.append((gnfile, linenr, new))
47 def AddHeadersNextToCC(headers, skip_ambiguous=True):
48 """Add header files next to the corresponding .cc files in GN files.
50 When skip_ambiguous is True, skip if multiple .cc files are found.
51 Returns unhandled headers.
53 Manual cleaning up is likely required, especially if not skip_ambiguous.
57 for filename in headers:
58 filename = filename.strip()
59 if not (filename.endswith('.h') or filename.endswith('.hh')):
61 basename = os.path.basename(filename)
63 cc = r'\b' + os.path.splitext(basename)[0] + r'\.(cc|cpp|mm)\b'
64 out, returncode = GitGrep('(/|")' + cc + '"')
65 if returncode != 0 or not out:
66 unhandled.append(filename)
69 matches = ValidMatches(basename, cc, out.splitlines())
74 print '\n[WARNING] Ambiguous matching for', filename
75 for i in enumerate(matches, 1):
76 print '%d: %s' % (i[0], i[1])
81 picked = raw_input('Pick the matches ("2,3" for multiple): ')
83 matches = [matches[int(i) - 1] for i in picked.split(',')]
84 except (ValueError, IndexError):
88 gnfile, linenr, new = match
89 print ' ', gnfile, linenr, new
90 edits.setdefault(gnfile, {})[linenr] = new
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')
101 def AddHeadersToSources(headers, skip_ambiguous=True):
102 """Add header files to the sources list in the first GN file.
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.
109 "git cl format" afterwards is required. Manually cleaning up duplicated items
112 for filename in headers:
113 filename = filename.strip()
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')
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
129 print ' ', gnfile, rel
131 lines.insert(index + 1, '"%s",' % rel)
132 open(gnfile, 'w').write('\n'.join(lines) + '\n')
135 def RemoveHeader(headers, skip_ambiguous=True):
136 """Remove non-existing headers in GN files.
138 When skip_ambiguous is True, skip if multiple matches are found.
142 for filename in headers:
143 filename = filename.strip()
144 if not (filename.endswith('.h') or filename.endswith('.hh')):
146 basename = os.path.basename(filename)
148 out, returncode = GitGrep('(/|")' + basename + '"')
149 if returncode != 0 or not out:
150 unhandled.append(filename)
154 grep_lines = out.splitlines()
156 for line in grep_lines:
157 gnfile, linenr, contents = line.split(':')
158 print ' ', gnfile, linenr, contents
160 lines = open(gnfile).read().splitlines()
161 assert contents in lines[linenr - 1]
162 matches.append((gnfile, linenr, contents))
164 if len(matches) == 0:
167 print '\n[WARNING] Ambiguous matching for', filename
168 for i in enumerate(matches, 1):
169 print '%d: %s' % (i[0], i[1])
174 picked = raw_input('Pick the matches ("2,3" for multiple): ')
176 matches = [matches[int(i) - 1] for i in picked.split(',')]
177 except (ValueError, IndexError):
180 for match in matches:
181 gnfile, linenr, contents = match
182 print ' ', gnfile, linenr, contents
183 edits.setdefault(gnfile, set()).add(linenr)
186 lines = open(gnfile).read().splitlines()
187 for l in sorted(edits[gnfile], reverse=True):
189 open(gnfile, 'w').write('\n'.join(lines) + '\n')
195 parser = argparse.ArgumentParser()
196 parser.add_argument('input_file', help="missing or non-existing headers, "
197 "output of check_gn_headers.py")
198 parser.add_argument('--prefix',
199 help="only handle path name with this prefix")
200 parser.add_argument('--remove', action='store_true',
201 help="treat input_file as non-existing headers")
203 args, _extras = parser.parse_known_args()
205 headers = open(args.input_file).readlines()
208 headers = [i for i in headers if i.startswith(args.prefix)]
211 RemoveHeader(headers, False)
213 unhandled = AddHeadersNextToCC(headers)
214 AddHeadersToSources(unhandled)
217 if __name__ == '__main__':