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