- add sources.
[platform/framework/web/crosswalk.git] / src / ppapi / PRESUBMIT.py
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 import os
6 import re
7 import sys
8 import subprocess
9
10
11 def RunCmdAndCheck(cmd, err_string, output_api, cwd=None):
12   results = []
13   p = subprocess.Popen(cmd, cwd=cwd,
14                        stdout=subprocess.PIPE,
15                        stderr=subprocess.PIPE)
16   (p_stdout, p_stderr) = p.communicate()
17   if p.returncode:
18     results.append(
19         output_api.PresubmitError(err_string,
20                                   long_text=p_stderr))
21   return results
22
23
24 def RunUnittests(input_api, output_api):
25   # Run some Generator unittests if the generator source was changed.
26   results = []
27   files = input_api.LocalPaths()
28   generator_files = []
29   for filename in files:
30     name_parts = filename.split(os.sep)
31     if name_parts[0:2] == ['ppapi', 'generators']:
32       generator_files.append(filename)
33   if generator_files != []:
34     cmd = [ sys.executable, 'idl_tests.py']
35     ppapi_dir = input_api.PresubmitLocalPath()
36     results.extend(RunCmdAndCheck(cmd,
37                                   'PPAPI IDL unittests failed.',
38                                   output_api,
39                                   os.path.join(ppapi_dir, 'generators')))
40   return results
41
42
43 # Verify that the files do not contain a 'TODO' in them.
44 RE_TODO = re.compile(r'\WTODO\W', flags=re.I)
45 def CheckTODO(input_api, output_api):
46   live_files = input_api.AffectedFiles(include_deletes=False)
47   files = [f.LocalPath() for f in live_files]
48   todo = []
49
50   for filename in files:
51     name, ext = os.path.splitext(filename)
52     name_parts = name.split(os.sep)
53
54     # Only check normal build sources.
55     if ext not in ['.h', '.idl']:
56       continue
57
58     # Only examine the ppapi directory.
59     if name_parts[0] != 'ppapi':
60       continue
61
62     # Only examine public plugin facing directories.
63     if name_parts[1] not in ['api', 'c', 'cpp', 'utility']:
64       continue
65
66     # Only examine public stable interfaces.
67     if name_parts[2] in ['dev', 'private', 'trusted']:
68       continue
69     if name_parts[2] == 'extensions' and name_parts[3] == 'dev':
70       continue
71
72     filepath = os.path.join('..', filename)
73     if RE_TODO.search(open(filepath, 'rb').read()):
74       todo.append(filename)
75
76   if todo:
77     return [output_api.PresubmitError(
78         'TODOs found in stable public PPAPI files:',
79         long_text='\n'.join(todo))]
80   return []
81
82 # Verify that no CPP wrappers use un-versioned PPB interface name macros.
83 RE_UNVERSIONED_PPB = re.compile(r'\bPPB_\w+_INTERFACE\b')
84 def CheckUnversionedPPB(input_api, output_api):
85   live_files = input_api.AffectedFiles(include_deletes=False)
86   files = [f.LocalPath() for f in live_files]
87   todo = []
88
89   for filename in files:
90     name, ext = os.path.splitext(filename)
91     name_parts = name.split(os.sep)
92
93     # Only check C++ sources.
94     if ext not in ['.cc']:
95       continue
96
97     # Only examine the public plugin facing ppapi/cpp directory.
98     if name_parts[0:2] != ['ppapi', 'cpp']:
99       continue
100
101     # Only examine public stable and trusted interfaces.
102     if name_parts[2] in ['dev', 'private']:
103       continue
104
105     filepath = os.path.join('..', filename)
106     if RE_UNVERSIONED_PPB.search(open(filepath, 'rb').read()):
107       todo.append(filename)
108
109   if todo:
110     return [output_api.PresubmitError(
111         'Unversioned PPB interface references found in PPAPI C++ wrappers:',
112         long_text='\n'.join(todo))]
113   return []
114
115 # Verify that changes to ppapi headers/sources are also made to NaCl SDK.
116 def CheckUpdatedNaClSDK(input_api, output_api):
117   files = input_api.LocalPaths()
118
119   # PPAPI files the Native Client SDK cares about.
120   nacl_sdk_files = []
121
122   for filename in files:
123     name, ext = os.path.splitext(filename)
124     name_parts = name.split(os.sep)
125
126     if len(name_parts) <= 2:
127       continue
128
129     if name_parts[0] != 'ppapi':
130       continue
131
132     if ((name_parts[1] == 'c' and ext == '.h') or
133         (name_parts[1] in ('cpp', 'utility') and ext in ('.h', '.cc'))):
134       if name_parts[2] in ('documentation', 'trusted'):
135         continue
136       nacl_sdk_files.append(filename)
137
138   if not nacl_sdk_files:
139     return []
140
141   verify_ppapi_py = os.path.join(input_api.change.RepositoryRoot(),
142                                  'native_client_sdk', 'src', 'build_tools',
143                                  'verify_ppapi.py')
144   cmd = [sys.executable, verify_ppapi_py] + nacl_sdk_files
145   return RunCmdAndCheck(cmd,
146                         'PPAPI Interface modified without updating NaCl SDK.',
147                         output_api)
148
149 def CheckChange(input_api, output_api):
150   results = []
151
152   results.extend(RunUnittests(input_api, output_api))
153
154   results.extend(CheckTODO(input_api, output_api))
155
156   results.extend(CheckUnversionedPPB(input_api, output_api))
157
158   results.extend(CheckUpdatedNaClSDK(input_api, output_api))
159
160   # Verify all modified *.idl have a matching *.h
161   files = input_api.LocalPaths()
162   h_files = []
163   idl_files = []
164   generators_changed = False
165
166   # Find all relevant .h and .idl files.
167   for filename in files:
168     name, ext = os.path.splitext(filename)
169     name_parts = name.split(os.sep)
170     if name_parts[0:2] == ['ppapi', 'c'] and ext == '.h':
171       h_files.append('/'.join(name_parts[2:]))
172     elif name_parts[0:2] == ['ppapi', 'api'] and ext == '.idl':
173       idl_files.append('/'.join(name_parts[2:]))
174     elif name_parts[0:2] == ['ppapi', 'generators']:
175       generators_changed = True
176
177   # Generate a list of all appropriate *.h and *.idl changes in this CL.
178   both = h_files + idl_files
179
180   # If there aren't any, we are done checking.
181   if not both: return results
182
183   missing = []
184   for filename in idl_files:
185     if filename not in set(h_files):
186       missing.append('ppapi/api/%s.idl' % filename)
187
188   # An IDL change that includes [generate_thunk] doesn't need to have
189   # an update to the corresponding .h file.
190   new_thunk_files = []
191   for filename in missing:
192     lines = input_api.RightHandSideLines(lambda f: f.LocalPath() == filename)
193     for line in lines:
194       if line[2].strip() == '[generate_thunk]':
195         new_thunk_files.append(filename)
196   for filename in new_thunk_files:
197     missing.remove(filename)
198
199   if missing:
200     results.append(
201         output_api.PresubmitPromptWarning(
202             'Missing PPAPI header, no change or skipped generation?',
203             long_text='\n  '.join(missing)))
204
205   missing_dev = []
206   missing_stable = []
207   missing_priv = []
208   for filename in h_files:
209     if filename not in set(idl_files):
210       name_parts = filename.split(os.sep)
211
212       if name_parts[-1] == 'pp_macros':
213         # The C header generator adds a PPAPI_RELEASE macro based on all the
214         # IDL files, so pp_macros.h may change while its IDL does not.
215         lines = input_api.RightHandSideLines(
216             lambda f: f.LocalPath() == 'ppapi/c/%s.h' % filename)
217         releaseChanged = False
218         for line in lines:
219           if line[2].split()[:2] == ['#define', 'PPAPI_RELEASE']:
220             results.append(
221                 output_api.PresubmitPromptOrNotify(
222                     'PPAPI_RELEASE has changed', long_text=line[2]))
223             releaseChanged = True
224             break
225         if releaseChanged:
226           continue
227
228       if 'trusted' in name_parts:
229         missing_priv.append('  ppapi/c/%s.h' % filename)
230         continue
231
232       if 'private' in name_parts:
233         missing_priv.append('  ppapi/c/%s.h' % filename)
234         continue
235
236       if 'dev' in name_parts:
237         missing_dev.append('  ppapi/c/%s.h' % filename)
238         continue
239
240       missing_stable.append('  ppapi/c/%s.h' % filename)
241
242   if missing_priv:
243     results.append(
244         output_api.PresubmitPromptWarning(
245             'Missing PPAPI IDL for private interface, please generate IDL:',
246             long_text='\n'.join(missing_priv)))
247
248   if missing_dev:
249     results.append(
250         output_api.PresubmitPromptWarning(
251             'Missing PPAPI IDL for DEV, required before moving to stable:',
252             long_text='\n'.join(missing_dev)))
253
254   if missing_stable:
255     # It might be okay that the header changed without a corresponding IDL
256     # change. E.g., comment indenting may have been changed. Treat this as a
257     # warning.
258     if generators_changed:
259       results.append(
260           output_api.PresubmitPromptWarning(
261               'Missing PPAPI IDL for stable interface (due to change in ' +
262               'generators?):',
263               long_text='\n'.join(missing_stable)))
264     else:
265       results.append(
266           output_api.PresubmitError(
267               'Missing PPAPI IDL for stable interface:',
268               long_text='\n'.join(missing_stable)))
269
270   # Verify all *.h files match *.idl definitions, use:
271   #   --test to prevent output to disk
272   #   --diff to generate a unified diff
273   #   --out to pick which files to examine (only the ones in the CL)
274   ppapi_dir = input_api.PresubmitLocalPath()
275   cmd = [sys.executable, 'generator.py',
276          '--wnone', '--diff', '--test','--cgen', '--range=start,end']
277
278   # Only generate output for IDL files references (as *.h or *.idl) in this CL
279   cmd.append('--out=' + ','.join([name + '.idl' for name in both]))
280   cmd_results = RunCmdAndCheck(cmd,
281                                'PPAPI IDL Diff detected: Run the generator.',
282                                output_api,
283                                os.path.join(ppapi_dir, 'generators'))
284   if cmd_results:
285     results.extend(cmd_results)
286
287   return results
288
289
290 def CheckChangeOnUpload(input_api, output_api):
291   return CheckChange(input_api, output_api)
292
293
294 def CheckChangeOnCommit(input_api, output_api):
295   return CheckChange(input_api, output_api)