[M73 Dev][EFL] Fix errors to generate ninja files
[platform/framework/web/chromium-efl.git] / build / fix_gn_headers.py
1 #!/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
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.
11 """
12
13 import argparse
14 import os
15 import re
16 import subprocess
17 import sys
18
19
20 def 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
28 def 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
47 def 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'
64     out, returncode = GitGrep('(/|")' + cc + '"')
65     if returncode != 0 or not out:
66       unhandled.append(filename)
67       continue
68
69     matches = ValidMatches(basename, cc, out.splitlines())
70
71     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:
79         continue
80
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):
85         continue
86
87     for match in matches:
88       gnfile, linenr, new = match
89       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
101 def 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
135 def RemoveHeader(headers, skip_ambiguous=True):
136   """Remove non-existing headers in GN files.
137
138   When skip_ambiguous is True, skip if multiple matches are found.
139   """
140   edits = {}
141   unhandled = []
142   for filename in headers:
143     filename = filename.strip()
144     if not (filename.endswith('.h') or filename.endswith('.hh')):
145       continue
146     basename = os.path.basename(filename)
147     print filename
148     out, returncode = GitGrep('(/|")' + basename + '"')
149     if returncode != 0 or not out:
150       unhandled.append(filename)
151       print '  Not found'
152       continue
153
154     grep_lines = out.splitlines()
155     matches = []
156     for line in grep_lines:
157       gnfile, linenr, contents = line.split(':')
158       print '    ', gnfile, linenr, contents
159       linenr = int(linenr)
160       lines = open(gnfile).read().splitlines()
161       assert contents in lines[linenr - 1]
162       matches.append((gnfile, linenr, contents))
163
164     if len(matches) == 0:
165       continue
166     if len(matches) > 1:
167       print '\n[WARNING] Ambiguous matching for', filename
168       for i in enumerate(matches, 1):
169         print '%d: %s' % (i[0], i[1])
170       print
171       if skip_ambiguous:
172         continue
173
174       picked = raw_input('Pick the matches ("2,3" for multiple): ')
175       try:
176         matches = [matches[int(i) - 1] for i in picked.split(',')]
177       except (ValueError, IndexError):
178         continue
179
180     for match in matches:
181       gnfile, linenr, contents = match
182       print '  ', gnfile, linenr, contents
183       edits.setdefault(gnfile, set()).add(linenr)
184
185   for gnfile in edits:
186     lines = open(gnfile).read().splitlines()
187     for l in sorted(edits[gnfile], reverse=True):
188       lines.pop(l - 1)
189     open(gnfile, 'w').write('\n'.join(lines) + '\n')
190
191   return unhandled
192
193
194 def main():
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")
202
203   args, _extras = parser.parse_known_args()
204
205   headers = open(args.input_file).readlines()
206
207   if args.prefix:
208     headers = [i for i in headers if i.startswith(args.prefix)]
209
210   if args.remove:
211     RemoveHeader(headers, False)
212   else:
213     unhandled = AddHeadersNextToCC(headers)
214     AddHeadersToSources(unhandled)
215
216
217 if __name__ == '__main__':
218   sys.exit(main())