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
5import os
6import re
7import sys
8import subprocess
9
10
11def RunCmdAndCheck(cmd, err_string, output_api, cwd=None, warning=False):
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    if warning:
19      results.append(output_api.PresubmitPromptWarning(
20        '%s\n\n%s' % (err_string, p_stderr)))
21    else:
22      results.append(
23          output_api.PresubmitError(err_string,
24                                    long_text=p_stderr))
25  return results
26
27
28def RunUnittests(input_api, output_api):
29  # Run some Generator unittests if the generator source was changed.
30  results = []
31  files = input_api.LocalPaths()
32  generator_files = []
33  for filename in files:
34    name_parts = filename.split(os.sep)
35    if name_parts[0:2] == ['ppapi', 'generators']:
36      generator_files.append(filename)
37  if generator_files != []:
38    cmd = [ sys.executable, 'idl_tests.py']
39    ppapi_dir = input_api.PresubmitLocalPath()
40    results.extend(RunCmdAndCheck(cmd,
41                                  'PPAPI IDL unittests failed.',
42                                  output_api,
43                                  os.path.join(ppapi_dir, 'generators')))
44  return results
45
46
47# Verify that the files do not contain a 'TODO' in them.
48RE_TODO = re.compile(r'\WTODO\W', flags=re.I)
49def CheckTODO(input_api, output_api):
50  live_files = input_api.AffectedFiles(include_deletes=False)
51  files = [f.LocalPath() for f in live_files]
52  todo = []
53
54  for filename in files:
55    name, ext = os.path.splitext(filename)
56    name_parts = name.split(os.sep)
57
58    # Only check normal build sources.
59    if ext not in ['.h', '.idl']:
60      continue
61
62    # Only examine the ppapi directory.
63    if name_parts[0] != 'ppapi':
64      continue
65
66    # Only examine public plugin facing directories.
67    if name_parts[1] not in ['api', 'c', 'cpp', 'utility']:
68      continue
69
70    # Only examine public stable interfaces.
71    if name_parts[2] in ['dev', 'private', 'trusted']:
72      continue
73
74    filepath = os.path.join('..', filename)
75    if RE_TODO.search(open(filepath, 'rb').read()):
76      todo.append(filename)
77
78  if todo:
79    return [output_api.PresubmitError(
80        'TODOs found in stable public PPAPI files:',
81        long_text='\n'.join(todo))]
82  return []
83
84# Verify that no CPP wrappers use un-versioned PPB interface name macros.
85RE_UNVERSIONED_PPB = re.compile(r'\bPPB_\w+_INTERFACE\b')
86def CheckUnversionedPPB(input_api, output_api):
87  live_files = input_api.AffectedFiles(include_deletes=False)
88  files = [f.LocalPath() for f in live_files]
89  todo = []
90
91  for filename in files:
92    name, ext = os.path.splitext(filename)
93    name_parts = name.split(os.sep)
94
95    # Only check C++ sources.
96    if ext not in ['.cc']:
97      continue
98
99    # Only examine the public plugin facing ppapi/cpp directory.
100    if name_parts[0:2] != ['ppapi', 'cpp']:
101      continue
102
103    # Only examine public stable and trusted interfaces.
104    if name_parts[2] in ['dev', 'private']:
105      continue
106
107    filepath = os.path.join('..', filename)
108    if RE_UNVERSIONED_PPB.search(open(filepath, 'rb').read()):
109      todo.append(filename)
110
111  if todo:
112    return [output_api.PresubmitError(
113        'Unversioned PPB interface references found in PPAPI C++ wrappers:',
114        long_text='\n'.join(todo))]
115  return []
116
117# Verify that changes to ppapi headers/sources are also made to NaCl SDK.
118def CheckUpdatedNaClSDK(input_api, output_api):
119  files = input_api.LocalPaths()
120
121  # PPAPI files the Native Client SDK cares about.
122  nacl_sdk_files = []
123
124  for filename in files:
125    name, ext = os.path.splitext(filename)
126    name_parts = name.split(os.sep)
127
128    if len(name_parts) <= 2:
129      continue
130
131    if name_parts[0] != 'ppapi':
132      continue
133
134    if ((name_parts[1] == 'c' and ext == '.h') or
135        (name_parts[1] in ('cpp', 'utility') and ext in ('.h', '.cc'))):
136      if name_parts[2] in ('documentation', 'trusted'):
137        continue
138      nacl_sdk_files.append(filename)
139
140  if not nacl_sdk_files:
141    return []
142
143  verify_ppapi_py = os.path.join(input_api.change.RepositoryRoot(),
144                                 'native_client_sdk', 'src', 'build_tools',
145                                 'verify_ppapi.py')
146  cmd = [sys.executable, verify_ppapi_py] + nacl_sdk_files
147  return RunCmdAndCheck(cmd,
148                        'PPAPI Interface modified without updating NaCl SDK.\n'
149                        '(note that some dev interfaces should not be added '
150                        'the NaCl SDK; when in doubt, ask a ppapi OWNER.\n'
151                        'To ignore a file, add it to IGNORED_FILES in '
152                        'native_client_sdk/src/build_tools/verify_ppapi.py)',
153                        output_api,
154                        warning=True)
155
156# Verify that changes to ppapi/thunk/interfaces_* files have a corresponding
157# change to tools/metrics/histograms/enums.xml for UMA tracking.
158def CheckHistogramXml(input_api, output_api):
159  # We can't use input_api.LocalPaths() here because we need to know about
160  # changes outside of ppapi/. See tools/depot_tools/presubmit_support.py for
161  # details on input_api.
162  files = input_api.change.AffectedFiles()
163
164  INTERFACE_FILES = ('ppapi/thunk/interfaces_legacy.h',
165                     'ppapi/thunk/interfaces_ppb_private_flash.h',
166                     'ppapi/thunk/interfaces_ppb_private.h',
167                     'ppapi/thunk/interfaces_ppb_private_no_permissions.h',
168                     'ppapi/thunk/interfaces_ppb_public_dev_channel.h',
169                     'ppapi/thunk/interfaces_ppb_public_dev.h',
170                     'ppapi/thunk/interfaces_ppb_public_stable.h',
171                     'ppapi/thunk/interfaces_ppb_public_socket.h')
172  HISTOGRAM_XML_FILE = 'tools/metrics/histograms/enums.xml'
173  interface_changes = []
174  has_histogram_xml_change = False
175  for filename in files:
176    path = filename.LocalPath()
177    if path in INTERFACE_FILES:
178      interface_changes.append(path)
179    if path == HISTOGRAM_XML_FILE:
180      has_histogram_xml_change = True
181
182  if interface_changes and not has_histogram_xml_change:
183    return [output_api.PresubmitNotifyResult(
184        'Missing change to tools/metrics/histograms/enums.xml.\n' +
185        'Run pepper_hash_for_uma to make get values for new interfaces.\n' +
186        'Interface changes:\n' + '\n'.join(interface_changes))]
187  return []
188
189def CheckChange(input_api, output_api):
190  results = []
191
192  results.extend(RunUnittests(input_api, output_api))
193
194  results.extend(CheckTODO(input_api, output_api))
195
196  results.extend(CheckUnversionedPPB(input_api, output_api))
197
198  results.extend(CheckUpdatedNaClSDK(input_api, output_api))
199
200  results.extend(CheckHistogramXml(input_api, output_api))
201
202  # Verify all modified *.idl have a matching *.h
203  files = input_api.LocalPaths()
204  h_files = []
205  idl_files = []
206  generators_changed = False
207
208  # These are autogenerated by the command buffer generator, they don't go
209  # through idl.
210  whitelist = ['ppb_opengles2', 'ppb_opengles2ext_dev']
211
212  # The PDF interface is hand-written.
213  whitelist += ['ppb_pdf', 'ppp_pdf']
214
215  # Find all relevant .h and .idl files.
216  for filename in files:
217    name, ext = os.path.splitext(filename)
218    name_parts = name.split(os.sep)
219    if name_parts[-1] in whitelist:
220      continue
221    if name_parts[0:2] == ['ppapi', 'c'] and ext == '.h':
222      h_files.append('/'.join(name_parts[2:]))
223    elif name_parts[0:2] == ['ppapi', 'api'] and ext == '.idl':
224      idl_files.append('/'.join(name_parts[2:]))
225    elif name_parts[0:2] == ['ppapi', 'generators']:
226      generators_changed = True
227
228  # Generate a list of all appropriate *.h and *.idl changes in this CL.
229  both = h_files + idl_files
230
231  # If there aren't any, we are done checking.
232  if not both: return results
233
234  missing = []
235  for filename in idl_files:
236    if filename not in set(h_files):
237      missing.append('ppapi/api/%s.idl' % filename)
238
239  # An IDL change that includes [generate_thunk] doesn't need to have
240  # an update to the corresponding .h file.
241  new_thunk_files = []
242  for filename in missing:
243    lines = input_api.RightHandSideLines(lambda f: f.LocalPath() == filename)
244    for line in lines:
245      if line[2].strip() == '[generate_thunk]':
246        new_thunk_files.append(filename)
247  for filename in new_thunk_files:
248    missing.remove(filename)
249
250  if missing:
251    results.append(
252        output_api.PresubmitPromptWarning(
253            'Missing PPAPI header, no change or skipped generation?',
254            long_text='\n  '.join(missing)))
255
256  missing_dev = []
257  missing_stable = []
258  missing_priv = []
259  for filename in h_files:
260    if filename not in set(idl_files):
261      name_parts = filename.split(os.sep)
262
263      if name_parts[-1] == 'pp_macros':
264        # The C header generator adds a PPAPI_RELEASE macro based on all the
265        # IDL files, so pp_macros.h may change while its IDL does not.
266        lines = input_api.RightHandSideLines(
267            lambda f: f.LocalPath() == 'ppapi/c/%s.h' % filename)
268        releaseChanged = False
269        for line in lines:
270          if line[2].split()[:2] == ['#define', 'PPAPI_RELEASE']:
271            results.append(
272                output_api.PresubmitPromptOrNotify(
273                    'PPAPI_RELEASE has changed', long_text=line[2]))
274            releaseChanged = True
275            break
276        if releaseChanged:
277          continue
278
279      if 'trusted' in name_parts:
280        missing_priv.append('  ppapi/c/%s.h' % filename)
281        continue
282
283      if 'private' in name_parts:
284        missing_priv.append('  ppapi/c/%s.h' % filename)
285        continue
286
287      if 'dev' in name_parts:
288        missing_dev.append('  ppapi/c/%s.h' % filename)
289        continue
290
291      missing_stable.append('  ppapi/c/%s.h' % filename)
292
293  if missing_priv:
294    results.append(
295        output_api.PresubmitPromptWarning(
296            'Missing PPAPI IDL for private interface, please generate IDL:',
297            long_text='\n'.join(missing_priv)))
298
299  if missing_dev:
300    results.append(
301        output_api.PresubmitPromptWarning(
302            'Missing PPAPI IDL for DEV, required before moving to stable:',
303            long_text='\n'.join(missing_dev)))
304
305  if missing_stable:
306    # It might be okay that the header changed without a corresponding IDL
307    # change. E.g., comment indenting may have been changed. Treat this as a
308    # warning.
309    if generators_changed:
310      results.append(
311          output_api.PresubmitPromptWarning(
312              'Missing PPAPI IDL for stable interface (due to change in ' +
313              'generators?):',
314              long_text='\n'.join(missing_stable)))
315    else:
316      results.append(
317          output_api.PresubmitError(
318              'Missing PPAPI IDL for stable interface:',
319              long_text='\n'.join(missing_stable)))
320
321  # Verify all *.h files match *.idl definitions, use:
322  #   --test to prevent output to disk
323  #   --diff to generate a unified diff
324  #   --out to pick which files to examine (only the ones in the CL)
325  ppapi_dir = input_api.PresubmitLocalPath()
326  cmd = [sys.executable, 'generator.py',
327         '--wnone', '--diff', '--test','--cgen', '--range=start,end']
328
329  # Only generate output for IDL files references (as *.h or *.idl) in this CL
330  cmd.append('--out=' + ','.join([name + '.idl' for name in both]))
331  cmd_results = RunCmdAndCheck(cmd,
332                               'PPAPI IDL Diff detected: Run the generator.',
333                               output_api,
334                               os.path.join(ppapi_dir, 'generators'))
335  if cmd_results:
336    results.extend(cmd_results)
337
338  return results
339
340
341def CheckChangeOnUpload(input_api, output_api):
342  return CheckChange(input_api, output_api)
343
344
345def CheckChangeOnCommit(input_api, output_api):
346  return CheckChange(input_api, output_api)
347