• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (C) 2017 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from __future__ import print_function
16import itertools
17import subprocess
18import time
19
20USE_PYTHON3 = True
21
22
23def RunAndReportIfLong(func, *args, **kargs):
24  start = time.time()
25  results = func(*args, **kargs)
26  end = time.time()
27  limit = 0.5  # seconds
28  name = func.__name__
29  runtime = end - start
30  if runtime > limit:
31    print("{} took >{:.2}s ({:.2}s)".format(name, limit, runtime))
32  return results
33
34
35def CheckChange(input, output):
36  # There apparently is no way to wrap strings in blueprints, so ignore long
37  # lines in them.
38  def long_line_sources(x):
39    return input.FilterSourceFile(
40        x,
41        files_to_check='.*',
42        files_to_skip=[
43            'Android[.]bp',
44            "buildtools/grpc/BUILD.gn",
45            '.*[.]json$',
46            '.*[.]sql$',
47            '.*[.]out$',
48            'test/trace_processor/.*/tests.*$',
49            '(.*/)?BUILD$',
50            'WORKSPACE',
51            '.*/Makefile$',
52            '/perfetto_build_flags.h$',
53            "infra/luci/.*",
54            "^ui/.*\.[jt]s$",  # TS/JS handled by eslint
55        ])
56
57  results = []
58  results += RunAndReportIfLong(input.canned_checks.CheckDoNotSubmit, input,
59                                output)
60  results += RunAndReportIfLong(input.canned_checks.CheckChangeHasNoTabs, input,
61                                output)
62  results += RunAndReportIfLong(
63      input.canned_checks.CheckLongLines,
64      input,
65      output,
66      80,
67      source_file_filter=long_line_sources)
68  # TS/JS handled by eslint
69  results += RunAndReportIfLong(
70      input.canned_checks.CheckPatchFormatted, input, output, check_js=False)
71  results += RunAndReportIfLong(input.canned_checks.CheckGNFormatted, input,
72                                output)
73  results += RunAndReportIfLong(CheckIncludeGuards, input, output)
74  results += RunAndReportIfLong(CheckIncludeViolations, input, output)
75  results += RunAndReportIfLong(CheckIncludePaths, input, output)
76  results += RunAndReportIfLong(CheckProtoComments, input, output)
77  results += RunAndReportIfLong(CheckBuild, input, output)
78  results += RunAndReportIfLong(CheckAndroidBlueprint, input, output)
79  results += RunAndReportIfLong(CheckBinaryDescriptors, input, output)
80  results += RunAndReportIfLong(CheckMergedTraceConfigProto, input, output)
81  results += RunAndReportIfLong(CheckProtoEventList, input, output)
82  results += RunAndReportIfLong(CheckBannedCpp, input, output)
83  results += RunAndReportIfLong(CheckBadCppPatterns, input, output)
84  results += RunAndReportIfLong(CheckSqlModules, input, output)
85  results += RunAndReportIfLong(CheckSqlMetrics, input, output)
86  results += RunAndReportIfLong(CheckTestData, input, output)
87  results += RunAndReportIfLong(CheckAmalgamatedPythonTools, input, output)
88  results += RunAndReportIfLong(CheckChromeStdlib, input, output)
89  results += RunAndReportIfLong(CheckAbsolutePathsInGn, input, output)
90  return results
91
92
93def CheckChangeOnUpload(input_api, output_api):
94  return CheckChange(input_api, output_api)
95
96
97def CheckChangeOnCommit(input_api, output_api):
98  return CheckChange(input_api, output_api)
99
100
101def CheckBuild(input_api, output_api):
102  # The script invocation doesn't work on Windows.
103  if input_api.is_windows:
104    return []
105
106  tool = 'tools/gen_bazel'
107
108  # If no GN files were modified, bail out.
109  def build_file_filter(x):
110    return input_api.FilterSourceFile(
111        x, files_to_check=('.*BUILD[.]gn$', '.*[.]gni$', 'BUILD\.extras', tool))
112
113  if not input_api.AffectedSourceFiles(build_file_filter):
114    return []
115  if subprocess.call([tool, '--check-only']):
116    return [
117        output_api.PresubmitError('Bazel BUILD(s) are out of date. Run ' +
118                                  tool + ' to update them.')
119    ]
120  return []
121
122
123def CheckAndroidBlueprint(input_api, output_api):
124  # The script invocation doesn't work on Windows.
125  if input_api.is_windows:
126    return []
127
128  tool = 'tools/gen_android_bp'
129
130  # If no GN files were modified, bail out.
131  def build_file_filter(x):
132    return input_api.FilterSourceFile(
133        x,
134        files_to_check=('.*BUILD[.]gn$', '.*[.]gni$', tool),
135        # Do not require Android.bp to be regenerated for chrome
136        # stdlib changes.
137        files_to_skip=(
138            'src/trace_processor/perfetto_sql/stdlib/chrome/BUILD.gn'))
139
140  if not input_api.AffectedSourceFiles(build_file_filter):
141    return []
142  if subprocess.call([tool, '--check-only']):
143    return [
144        output_api.PresubmitError('Android build files are out of date. ' +
145                                  'Run ' + tool + ' to update them.')
146    ]
147  return []
148
149
150def CheckIncludeGuards(input_api, output_api):
151  # The script invocation doesn't work on Windows.
152  if input_api.is_windows:
153    return []
154
155  tool = 'tools/fix_include_guards'
156
157  def file_filter(x):
158    return input_api.FilterSourceFile(
159        x, files_to_check=['.*[.]cc$', '.*[.]h$', tool])
160
161  if not input_api.AffectedSourceFiles(file_filter):
162    return []
163  if subprocess.call([tool, '--check-only']):
164    return [
165        output_api.PresubmitError('Please run ' + tool +
166                                  ' to fix include guards.')
167    ]
168  return []
169
170
171def CheckBannedCpp(input_api, output_api):
172  bad_cpp = [
173      (r'\bstd::stoi\b',
174       'std::stoi throws exceptions prefer base::StringToInt32()'),
175      (r'\bstd::stol\b',
176       'std::stoull throws exceptions prefer base::StringToInt32()'),
177      (r'\bstd::stoul\b',
178       'std::stoull throws exceptions prefer base::StringToUint32()'),
179      (r'\bstd::stoll\b',
180       'std::stoull throws exceptions prefer base::StringToInt64()'),
181      (r'\bstd::stoull\b',
182       'std::stoull throws exceptions prefer base::StringToUint64()'),
183      (r'\bstd::stof\b',
184       'std::stof throws exceptions prefer base::StringToDouble()'),
185      (r'\bstd::stod\b',
186       'std::stod throws exceptions prefer base::StringToDouble()'),
187      (r'\bstd::stold\b',
188       'std::stold throws exceptions prefer base::StringToDouble()'),
189      (r'\bstrncpy\b',
190       'strncpy does not null-terminate if src > dst. Use base::StringCopy'),
191      (r'[(=]\s*snprintf\(',
192       'snprintf can return > dst_size. Use base::SprintfTrunc'),
193      (r'//.*\bDNS\b',
194       '// DNS (Do Not Ship) found. Did you mean to remove some testing code?'),
195      (r'\bPERFETTO_EINTR\(close\(',
196       'close(2) must not be retried on EINTR on Linux and other OSes '
197       'that we run on, as the fd will be closed.'),
198      (r'^#include <inttypes.h>', 'Use <cinttypes> rather than <inttypes.h>. ' +
199       'See https://github.com/google/perfetto/issues/146'),
200  ]
201
202  def file_filter(x):
203    return input_api.FilterSourceFile(x, files_to_check=[r'.*\.h$', r'.*\.cc$'])
204
205  errors = []
206  for f in input_api.AffectedSourceFiles(file_filter):
207    for line_number, line in f.ChangedContents():
208      if input_api.re.search(r'^\s*//', line):
209        continue  # Skip comments
210      for regex, message in bad_cpp:
211        if input_api.re.search(regex, line):
212          errors.append(
213              output_api.PresubmitError('Banned pattern:\n  {}:{} {}'.format(
214                  f.LocalPath(), line_number, message)))
215  return errors
216
217
218def CheckBadCppPatterns(input_api, output_api):
219  bad_patterns = [
220      (r'.*/tracing_service_impl[.]cc$', r'\btrigger_config\(\)',
221       'Use GetTriggerMode(session->config) rather than .trigger_config()'),
222  ]
223  errors = []
224  for file_regex, code_regex, message in bad_patterns:
225    filt = lambda x: input_api.FilterSourceFile(x, files_to_check=[file_regex])
226    for f in input_api.AffectedSourceFiles(filt):
227      for line_number, line in f.ChangedContents():
228        if input_api.re.search(r'^\s*//', line):
229          continue  # Skip comments
230        if input_api.re.search(code_regex, line):
231          errors.append(
232              output_api.PresubmitError('{}:{} {}'.format(
233                  f.LocalPath(), line_number, message)))
234  return errors
235
236
237def CheckIncludeViolations(input_api, output_api):
238  # The script invocation doesn't work on Windows.
239  if input_api.is_windows:
240    return []
241
242  tool = 'tools/check_include_violations'
243
244  def file_filter(x):
245    return input_api.FilterSourceFile(
246        x, files_to_check=['include/.*[.]h$', tool])
247
248  if not input_api.AffectedSourceFiles(file_filter):
249    return []
250  if subprocess.call([tool]):
251    return [output_api.PresubmitError(tool + ' failed.')]
252  return []
253
254
255def CheckIncludePaths(input_api, output_api):
256
257  def file_filter(x):
258    return input_api.FilterSourceFile(x, files_to_check=[r'.*\.h$', r'.*\.cc$'])
259
260  error_lines = []
261  for f in input_api.AffectedSourceFiles(file_filter):
262    for line_num, line in f.ChangedContents():
263      m = input_api.re.search(r'^#include "(.*\.h)"', line)
264      if not m:
265        continue
266      inc_hdr = m.group(1)
267      if inc_hdr.startswith('include/perfetto'):
268        error_lines.append('  %s:%s: Redundant "include/" in #include path"' %
269                           (f.LocalPath(), line_num))
270      if '/' not in inc_hdr:
271        error_lines.append(
272            '  %s:%s: relative #include not allowed, use full path' %
273            (f.LocalPath(), line_num))
274  return [] if len(error_lines) == 0 else [
275      output_api.PresubmitError('Invalid #include paths detected:\n' +
276                                '\n'.join(error_lines))
277  ]
278
279
280def CheckBinaryDescriptors(input_api, output_api):
281  # The script invocation doesn't work on Windows.
282  if input_api.is_windows:
283    return []
284
285  tool = 'tools/gen_binary_descriptors'
286
287  def file_filter(x):
288    return input_api.FilterSourceFile(
289        x, files_to_check=['protos/perfetto/.*[.]proto$', '.*[.]h', tool])
290
291  if not input_api.AffectedSourceFiles(file_filter):
292    return []
293  if subprocess.call([tool, '--check-only']):
294    return [
295        output_api.PresubmitError('Please run ' + tool +
296                                  ' to update binary descriptors.')
297    ]
298  return []
299
300
301def CheckMergedTraceConfigProto(input_api, output_api):
302  # The script invocation doesn't work on Windows.
303  if input_api.is_windows:
304    return []
305
306  tool = 'tools/gen_merged_protos'
307
308  def build_file_filter(x):
309    return input_api.FilterSourceFile(
310        x, files_to_check=['protos/perfetto/.*[.]proto$', tool])
311
312  if not input_api.AffectedSourceFiles(build_file_filter):
313    return []
314  if subprocess.call([tool, '--check-only']):
315    return [
316        output_api.PresubmitError(
317            'perfetto_config.proto or perfetto_trace.proto is out of ' +
318            'date. Please run ' + tool + ' to update it.')
319    ]
320  return []
321
322
323# Prevent removing or changing lines in event_list.
324def CheckProtoEventList(input_api, output_api):
325  for f in input_api.AffectedFiles():
326    if f.LocalPath() != 'src/tools/ftrace_proto_gen/event_list':
327      continue
328    if any((not new_line.startswith('removed')) and new_line != old_line
329           for old_line, new_line in zip(f.OldContents(), f.NewContents())):
330      return [
331          output_api.PresubmitError(
332              'event_list only has two supported changes: '
333              'appending a new line, and replacing a line with removed.')
334      ]
335  return []
336
337
338def CheckProtoComments(input_api, output_api):
339  # The script invocation doesn't work on Windows.
340  if input_api.is_windows:
341    return []
342
343  tool = 'tools/check_proto_comments'
344
345  def file_filter(x):
346    return input_api.FilterSourceFile(
347        x, files_to_check=['protos/perfetto/.*[.]proto$', tool])
348
349  if not input_api.AffectedSourceFiles(file_filter):
350    return []
351  if subprocess.call([tool]):
352    return [output_api.PresubmitError(tool + ' failed')]
353  return []
354
355
356def CheckSqlModules(input_api, output_api):
357  # The script invocation doesn't work on Windows.
358  if input_api.is_windows:
359    return []
360
361  tool = 'tools/check_sql_modules.py'
362
363  def file_filter(x):
364    return input_api.FilterSourceFile(
365        x,
366        files_to_check=[
367            'src/trace_processor/perfetto_sql/stdlib/.*[.]sql$', tool
368        ])
369
370  if not input_api.AffectedSourceFiles(file_filter):
371    return []
372  if subprocess.call([tool]):
373    return [output_api.PresubmitError(tool + ' failed')]
374  return []
375
376
377def CheckSqlMetrics(input_api, output_api):
378  # The script invocation doesn't work on Windows.
379  if input_api.is_windows:
380    return []
381
382  tool = 'tools/check_sql_metrics.py'
383
384  def file_filter(x):
385    return input_api.FilterSourceFile(
386        x, files_to_check=['src/trace_processor/metrics/.*[.]sql$', tool])
387
388  if not input_api.AffectedSourceFiles(file_filter):
389    return []
390  if subprocess.call([tool]):
391    return [output_api.PresubmitError(tool + ' failed')]
392  return []
393
394
395def CheckTestData(input_api, output_api):
396  # The script invocation doesn't work on Windows.
397  if input_api.is_windows:
398    return []
399
400  tool = 'tools/test_data'
401  if subprocess.call([tool, 'status', '--quiet']):
402    return [
403        output_api.PresubmitError(
404            '//test/data is out of sync. Run ' + tool + ' status for more. \n'
405            'If you rebaselined UI tests or added a new test trace, run:'
406            '`tools/test_data upload`. Otherwise run `tools/install-build-deps`'
407            ' or `tools/test_data download --overwrite` to sync local test_data'
408        )
409    ]
410  return []
411
412
413def CheckChromeStdlib(input_api, output_api):
414  stdlib_paths = ("src/trace_processor/perfetto_sql/stdlib/chrome/",
415                  "test/data/chrome/",
416                  "test/trace_processor/diff_tests/stdlib/chrome/")
417
418  def chrome_stdlib_file_filter(x):
419    return input_api.FilterSourceFile(x, files_to_check=stdlib_paths)
420
421  # Only check chrome stdlib files
422  if not any(input_api.AffectedFiles(file_filter=chrome_stdlib_file_filter)):
423    return []
424
425  # Always allow Copybara service to make changes to chrome stdlib
426  if input_api.change.COPYBARA_IMPORT:
427    return []
428
429  if input_api.change.CHROME_STDLIB_MANUAL_ROLL:
430    return []
431
432  message = (
433      'Files under {0} and {1} '
434      'are rolled from the Chromium repository by a '
435      'Copybara service.\nYou should not modify these in '
436      'the Perfetto repository, please make your changes '
437      'in Chromium instead.\n'
438      'If you want to do a manual roll, you must specify '
439      'CHROME_STDLIB_MANUAL_ROLL=<reason> in the CL description.').format(
440          *stdlib_paths)
441  return [output_api.PresubmitError(message)]
442
443
444def CheckAmalgamatedPythonTools(input_api, output_api):
445  # The script invocation doesn't work on Windows.
446  if input_api.is_windows:
447    return []
448
449  tool = 'tools/gen_amalgamated_python_tools'
450
451  # If no GN files were modified, bail out.
452  def build_file_filter(x):
453    return input_api.FilterSourceFile(x, files_to_check=('python/.*$', tool))
454
455  if not input_api.AffectedSourceFiles(build_file_filter):
456    return []
457  if subprocess.call([tool, '--check-only']):
458    return [
459        output_api.PresubmitError(
460            'amalgamated python tools/ are out of date. ' + 'Run ' + tool +
461            ' to update them.')
462    ]
463  return []
464
465
466def CheckAbsolutePathsInGn(input_api, output_api):
467
468  def file_filter(x):
469    return input_api.FilterSourceFile(
470        x,
471        files_to_check=[r'.*\.gni?$'],
472        files_to_skip=['^.gn$', '^gn/.*', '^buildtools/.*'])
473
474  error_lines = []
475  for f in input_api.AffectedSourceFiles(file_filter):
476    for line_number, line in f.ChangedContents():
477      if input_api.re.search(r'(^\s*[#])|([#]\s*nogncheck)', line):
478        continue  # Skip comments and '# nogncheck' lines
479      if input_api.re.search(r'"//[^"]', line):
480        error_lines.append('  %s:%s: %s' %
481                           (f.LocalPath(), line_number, line.strip()))
482
483  if len(error_lines) == 0:
484    return []
485  return [
486      output_api.PresubmitError(
487          'Use relative paths in GN rather than absolute:\n' +
488          '\n'.join(error_lines))
489  ]
490