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