1#!/usr/bin/env python3 2 3# Copyright 2020 The Pigweed Authors 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); you may not 6# use this file except in compliance with the License. You may obtain a copy of 7# the License at 8# 9# https://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14# License for the specific language governing permissions and limitations under 15# the License. 16"""Runs the local presubmit checks for the Pigweed repository.""" 17 18import argparse 19import logging 20import os 21from pathlib import Path 22import re 23import shutil 24import sys 25from typing import Sequence, IO, Tuple, Optional 26 27try: 28 import pw_presubmit 29except ImportError: 30 # Append the pw_presubmit package path to the module search path to allow 31 # running this module without installing the pw_presubmit package. 32 sys.path.append(os.path.dirname(os.path.dirname( 33 os.path.abspath(__file__)))) 34 import pw_presubmit 35 36import pw_package.pigweed_packages 37 38from pw_presubmit import build, cli, format_code, git_repo 39from pw_presubmit import call, filter_paths, plural, PresubmitContext 40from pw_presubmit import PresubmitFailure, Programs 41from pw_presubmit.install_hook import install_hook 42 43_LOG = logging.getLogger(__name__) 44 45pw_package.pigweed_packages.initialize() 46 47# Trigger builds if files with these extensions change. 48_BUILD_EXTENSIONS = ('.py', '.rst', '.gn', '.gni', 49 *format_code.C_FORMAT.extensions) 50 51 52def _at_all_optimization_levels(target): 53 levels = ('debug', 'size_optimized', 'speed_optimized') 54 55 # Skip optimized host GCC builds for now, since GCC sometimes emits spurious 56 # warnings. 57 # 58 # -02: GCC 9.3 emits spurious maybe-uninitialized warnings 59 # -0s: GCC 8.1 (Mingw-w64) emits a spurious nonnull warning 60 # 61 # TODO(pwbug/255): Enable optimized GCC builds when this is fixed. 62 if target == 'host_gcc': 63 levels = ('debug', ) 64 65 for level in levels: 66 yield f'{target}_{level}' 67 68 69# 70# Build presubmit checks 71# 72def gn_clang_build(ctx: PresubmitContext): 73 build.gn_gen(ctx.root, ctx.output_dir) 74 build.ninja(ctx.output_dir, *_at_all_optimization_levels('host_clang')) 75 76 77@filter_paths(endswith=_BUILD_EXTENSIONS) 78def gn_gcc_build(ctx: PresubmitContext): 79 build.gn_gen(ctx.root, ctx.output_dir) 80 build.ninja(ctx.output_dir, *_at_all_optimization_levels('host_gcc')) 81 82 83_HOST_COMPILER = 'gcc' if sys.platform == 'win32' else 'clang' 84 85 86def gn_host_build(ctx: PresubmitContext): 87 build.gn_gen(ctx.root, ctx.output_dir) 88 build.ninja(ctx.output_dir, 89 *_at_all_optimization_levels(f'host_{_HOST_COMPILER}')) 90 91 92@filter_paths(endswith=_BUILD_EXTENSIONS) 93def gn_quick_build_check(ctx: PresubmitContext): 94 build.gn_gen(ctx.root, ctx.output_dir) 95 96 # TODO(pwbug/255): Switch to optimized GCC builds when this is fixed. 97 # See comment in _at_all_optimization_levels() above for details. 98 optimization_level = 'size_optimized' 99 if _HOST_COMPILER == 'gcc': 100 optimization_level = 'debug' 101 102 build.ninja(ctx.output_dir, f'host_{_HOST_COMPILER}_{optimization_level}', 103 'stm32f429i_size_optimized', 'python.tests', 'python.lint') 104 105 106@filter_paths(endswith=_BUILD_EXTENSIONS) 107def gn_full_build_check(ctx: PresubmitContext): 108 build.gn_gen(ctx.root, ctx.output_dir) 109 build.ninja(ctx.output_dir, *_at_all_optimization_levels('stm32f429i'), 110 *_at_all_optimization_levels(f'host_{_HOST_COMPILER}'), 111 'python.tests', 'python.lint', 'docs') 112 113 114@filter_paths(endswith=_BUILD_EXTENSIONS) 115def gn_full_qemu_check(ctx: PresubmitContext): 116 build.gn_gen(ctx.root, ctx.output_dir) 117 build.ninja(ctx.output_dir, *_at_all_optimization_levels('qemu_gcc'), 118 *_at_all_optimization_levels('qemu_clang')) 119 120 121@filter_paths(endswith=_BUILD_EXTENSIONS) 122def gn_arm_build(ctx: PresubmitContext): 123 build.gn_gen(ctx.root, ctx.output_dir) 124 build.ninja(ctx.output_dir, *_at_all_optimization_levels('stm32f429i')) 125 126 127@filter_paths(endswith=_BUILD_EXTENSIONS) 128def gn_nanopb_build(ctx: PresubmitContext): 129 build.install_package(ctx.package_root, 'nanopb') 130 build.gn_gen(ctx.root, 131 ctx.output_dir, 132 dir_pw_third_party_nanopb='"{}"'.format(ctx.package_root / 133 'nanopb')) 134 build.ninja( 135 ctx.output_dir, 136 *_at_all_optimization_levels('stm32f429i'), 137 *_at_all_optimization_levels('host_clang'), 138 ) 139 140 141@filter_paths(endswith=_BUILD_EXTENSIONS) 142def gn_teensy_build(ctx: PresubmitContext): 143 build.install_package(ctx.package_root, 'teensy') 144 build.gn_gen(ctx.root, 145 ctx.output_dir, 146 pw_arduino_build_CORE_PATH='"{}"'.format(str( 147 ctx.package_root)), 148 pw_arduino_build_CORE_NAME='teensy', 149 pw_arduino_build_PACKAGE_NAME='teensy/avr', 150 pw_arduino_build_BOARD='teensy40') 151 build.ninja(ctx.output_dir, *_at_all_optimization_levels('arduino')) 152 153 154@filter_paths(endswith=_BUILD_EXTENSIONS) 155def gn_qemu_build(ctx: PresubmitContext): 156 build.gn_gen(ctx.root, ctx.output_dir) 157 build.ninja(ctx.output_dir, *_at_all_optimization_levels('qemu_gcc')) 158 159 160@filter_paths(endswith=_BUILD_EXTENSIONS) 161def gn_qemu_clang_build(ctx: PresubmitContext): 162 build.gn_gen(ctx.root, ctx.output_dir) 163 build.ninja(ctx.output_dir, *_at_all_optimization_levels('qemu_clang')) 164 165 166def gn_docs_build(ctx: PresubmitContext): 167 build.gn_gen(ctx.root, ctx.output_dir) 168 build.ninja(ctx.output_dir, 'docs') 169 170 171def gn_host_tools(ctx: PresubmitContext): 172 build.gn_gen(ctx.root, ctx.output_dir, pw_build_HOST_TOOLS=True) 173 build.ninja(ctx.output_dir, 'host') 174 175 176@filter_paths(endswith=format_code.C_FORMAT.extensions) 177def oss_fuzz_build(ctx: PresubmitContext): 178 build.gn_gen(ctx.root, ctx.output_dir, pw_toolchain_OSS_FUZZ_ENABLED=True) 179 build.ninja(ctx.output_dir, "fuzzers") 180 181 182@filter_paths(endswith='.py') 183def python_checks(ctx: PresubmitContext): 184 build.gn_gen(ctx.root, ctx.output_dir) 185 build.ninja( 186 ctx.output_dir, 187 ':python.lint', 188 ':python.tests', 189 ':target_support_packages.lint', 190 ':target_support_packages.tests', 191 ) 192 193 194def _run_cmake(ctx: PresubmitContext) -> None: 195 build.install_package(ctx.package_root, 'nanopb') 196 197 toolchain = ctx.root / 'pw_toolchain' / 'host_clang' / 'toolchain.cmake' 198 build.cmake(ctx.root, 199 ctx.output_dir, 200 f'-DCMAKE_TOOLCHAIN_FILE={toolchain}', 201 '-DCMAKE_EXPORT_COMPILE_COMMANDS=1', 202 f'-Ddir_pw_third_party_nanopb={ctx.package_root / "nanopb"}', 203 '-Dpw_third_party_nanopb_ADD_SUBDIRECTORY=ON', 204 env=build.env_with_clang_vars()) 205 206 207@filter_paths(endswith=(*format_code.C_FORMAT.extensions, '.cmake', 208 'CMakeLists.txt')) 209def cmake_tests(ctx: PresubmitContext): 210 _run_cmake(ctx) 211 build.ninja(ctx.output_dir, 'pw_apps', 'pw_run_tests.modules') 212 213 214# TODO: Slowly add modules here that work with bazel until all 215# modules are added. Then replace with //... 216_MODULES_THAT_WORK_WITH_BAZEL = [ 217 '//pw_assert_basic/...', 218 '//pw_base64/...', 219 '//pw_build/...', 220 '//pw_chrono_stl/...', 221 '//pw_containers/...', 222 '//pw_cpu_exception/...', 223 '//pw_docgen/...', 224 '//pw_doctor/...', 225 '//pw_i2c/...', 226 '//pw_log/...', 227 '//pw_log_basic/...', 228 '//pw_polyfill/...', 229 '//pw_preprocessor/...', 230 '//pw_protobuf_compiler/...', 231 '//pw_span/...', 232 '//pw_status/...', 233 '//pw_sys_io/...', 234 '//pw_sys_io_baremetal_lm3s6965evb/...', 235 '//pw_sys_io_stdio/...', 236 '//pw_thread_stl/...', 237 '//pw_toolchain/...', 238 '//pw_varint/...', 239 '//pw_web_ui/...', 240] 241 242 243@filter_paths(endswith=(*format_code.C_FORMAT.extensions, '.bzl', 'BUILD')) 244def bazel_test(ctx: PresubmitContext): 245 """Runs bazel test on each bazel compatible module""" 246 247 try: 248 call('bazel', 249 'test', 250 *_MODULES_THAT_WORK_WITH_BAZEL, 251 '--verbose_failures', 252 '--verbose_explanations', 253 '--worker_verbose', 254 '--test_output=errors', 255 cwd=ctx.root, 256 env=build.env_with_clang_vars()) 257 except: 258 _LOG.info('If the Bazel build inexplicably fails while the ' 259 'other builds are passing, try deleting the Bazel cache:\n' 260 ' rm -rf ~/.cache/bazel') 261 raise 262 263 264@filter_paths(endswith=(*format_code.C_FORMAT.extensions, '.bzl', 'BUILD')) 265def bazel_build(ctx: PresubmitContext): 266 """Runs Bazel build on each Bazel compatible module""" 267 try: 268 call('bazel', 269 'build', 270 *_MODULES_THAT_WORK_WITH_BAZEL, 271 '--verbose_failures', 272 '--verbose_explanations', 273 '--worker_verbose', 274 cwd=ctx.root, 275 env=build.env_with_clang_vars()) 276 except: 277 _LOG.info('If the Bazel build inexplicably fails while the ' 278 'other builds are passing, try deleting the Bazel cache:\n' 279 ' rm -rf ~/.cache/bazel') 280 raise 281 282 283# 284# General presubmit checks 285# 286 287# TODO(pwbug/45) Probably want additional checks. 288_CLANG_TIDY_CHECKS = ('modernize-use-override', ) 289 290 291@filter_paths(endswith=format_code.C_FORMAT.extensions) 292def clang_tidy(ctx: PresubmitContext): 293 build.gn_gen(ctx.root, ctx.output_dir, '--export-compile-commands') 294 build.ninja(ctx.output_dir) 295 build.ninja(ctx.output_dir, '-t', 'compdb', 'objcxx', 'cxx') 296 297 run_clang_tidy = None 298 for var in ('PW_PIGWEED_CIPD_INSTALL_DIR', 'PW_CIPD_INSTALL_DIR'): 299 if var in os.environ: 300 possibility = os.path.join(os.environ[var], 301 'share/clang/run-clang-tidy.py') 302 if os.path.isfile(possibility): 303 run_clang_tidy = possibility 304 break 305 306 checks = ','.join(_CLANG_TIDY_CHECKS) 307 call( 308 run_clang_tidy, 309 f'-p={ctx.output_dir}', 310 f'-checks={checks}', 311 # TODO(pwbug/45) not sure if this is needed. 312 # f'-extra-arg-before=-warnings-as-errors={checks}', 313 *ctx.paths) 314 315 316# The first line must be regex because of the '20\d\d' date 317COPYRIGHT_FIRST_LINE = r'Copyright 20\d\d The Pigweed Authors' 318COPYRIGHT_COMMENTS = r'(#|//| \*|REM|::)' 319COPYRIGHT_BLOCK_COMMENTS = ( 320 # HTML comments 321 (r'<!--', r'-->'), ) 322 323COPYRIGHT_FIRST_LINE_EXCEPTIONS = ( 324 '#!', 325 '/*', 326 '@echo off', 327 '# -*-', 328 ':', 329) 330 331COPYRIGHT_LINES = tuple("""\ 332 333Licensed under the Apache License, Version 2.0 (the "License"); you may not 334use this file except in compliance with the License. You may obtain a copy of 335the License at 336 337 https://www.apache.org/licenses/LICENSE-2.0 338 339Unless required by applicable law or agreed to in writing, software 340distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 341WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 342License for the specific language governing permissions and limitations under 343the License. 344""".splitlines()) 345 346_EXCLUDE_FROM_COPYRIGHT_NOTICE: Sequence[str] = ( 347 # Configuration 348 r'^(?:.+/)?\..+$', 349 r'\bPW_PLUGINS$', 350 # Metadata 351 r'^docker/tag$', 352 r'\bAUTHORS$', 353 r'\bLICENSE$', 354 r'\bOWNERS$', 355 r'\brequirements.txt$', 356 r'\bgo.(mod|sum)$', 357 r'\bpackage.json$', 358 r'\byarn.lock$', 359 # Data files 360 r'\.elf$', 361 r'\.gif$', 362 r'\.jpg$', 363 r'\.json$', 364 r'\.png$', 365 # Documentation 366 r'\.md$', 367 r'\.rst$', 368 # Generated protobuf files 369 r'\.pb\.h$', 370 r'\.pb\.c$', 371 r'\_pb2.pyi?$', 372 # Diff/Patch files 373 r'\.diff$', 374 r'\.patch$', 375) 376 377 378def match_block_comment_start(line: str) -> Optional[str]: 379 """Matches the start of a block comment and returns the end.""" 380 for block_comment in COPYRIGHT_BLOCK_COMMENTS: 381 if re.match(block_comment[0], line): 382 # Return the end of the block comment 383 return block_comment[1] 384 return None 385 386 387def copyright_read_first_line( 388 file: IO) -> Tuple[Optional[str], Optional[str], Optional[str]]: 389 """Reads the file until it reads a valid first copyright line. 390 391 Returns (comment, block_comment, line). comment and block_comment are 392 mutually exclusive and refer to the comment character sequence and whether 393 they form a block comment or a line comment. line is the first line of 394 the copyright, and is used for error reporting. 395 """ 396 line = file.readline() 397 first_line_matcher = re.compile(COPYRIGHT_COMMENTS + ' ' + 398 COPYRIGHT_FIRST_LINE) 399 while line: 400 end_block_comment = match_block_comment_start(line) 401 if end_block_comment: 402 next_line = file.readline() 403 copyright_line = re.match(COPYRIGHT_FIRST_LINE, next_line) 404 if not copyright_line: 405 return (None, None, line) 406 return (None, end_block_comment, line) 407 408 first_line = first_line_matcher.match(line) 409 if first_line: 410 return (first_line.group(1), None, line) 411 412 if (line.strip() 413 and not line.startswith(COPYRIGHT_FIRST_LINE_EXCEPTIONS)): 414 return (None, None, line) 415 416 line = file.readline() 417 return (None, None, None) 418 419 420@filter_paths(exclude=_EXCLUDE_FROM_COPYRIGHT_NOTICE) 421def copyright_notice(ctx: PresubmitContext): 422 """Checks that the Pigweed copyright notice is present.""" 423 errors = [] 424 425 for path in ctx.paths: 426 427 if path.stat().st_size == 0: 428 continue # Skip empty files 429 430 with path.open() as file: 431 (comment, end_block_comment, 432 line) = copyright_read_first_line(file) 433 434 if not line: 435 _LOG.warning('%s: invalid first line', path) 436 errors.append(path) 437 continue 438 439 if not (comment or end_block_comment): 440 _LOG.warning('%s: invalid first line %r', path, line) 441 errors.append(path) 442 continue 443 444 if end_block_comment: 445 expected_lines = COPYRIGHT_LINES + (end_block_comment, ) 446 else: 447 expected_lines = COPYRIGHT_LINES 448 449 for expected, actual in zip(expected_lines, file): 450 if end_block_comment: 451 expected_line = expected + '\n' 452 elif comment: 453 expected_line = (comment + ' ' + expected).rstrip() + '\n' 454 455 if expected_line != actual: 456 _LOG.warning(' bad line: %r', actual) 457 _LOG.warning(' expected: %r', expected_line) 458 errors.append(path) 459 break 460 461 if errors: 462 _LOG.warning('%s with a missing or incorrect copyright notice:\n%s', 463 plural(errors, 'file'), '\n'.join(str(e) for e in errors)) 464 raise PresubmitFailure 465 466 467_BAZEL_SOURCES_IN_BUILD = tuple(format_code.C_FORMAT.extensions) 468_GN_SOURCES_IN_BUILD = '.rst', '.py', *_BAZEL_SOURCES_IN_BUILD 469 470 471@filter_paths(endswith=(*_GN_SOURCES_IN_BUILD, 'BUILD', '.bzl', '.gn', '.gni')) 472def source_is_in_build_files(ctx: PresubmitContext): 473 """Checks that source files are in the GN and Bazel builds.""" 474 missing = build.check_builds_for_files( 475 _BAZEL_SOURCES_IN_BUILD, 476 _GN_SOURCES_IN_BUILD, 477 ctx.paths, 478 bazel_dirs=[ctx.root], 479 gn_build_files=git_repo.list_files(pathspecs=['BUILD.gn', '*BUILD.gn'], 480 repo_path=ctx.root)) 481 482 if missing: 483 _LOG.warning( 484 'All source files must appear in BUILD and BUILD.gn files') 485 raise PresubmitFailure 486 487 _run_cmake(ctx) 488 cmake_missing = build.check_compile_commands_for_files( 489 ctx.output_dir / 'compile_commands.json', 490 (f for f in ctx.paths if f.suffix in ('.c', '.cc'))) 491 if cmake_missing: 492 _LOG.warning('The CMake build is missing %d files', len(cmake_missing)) 493 _LOG.warning('Files missing from CMake:\n%s', 494 '\n'.join(str(f) for f in cmake_missing)) 495 # TODO(hepler): Many files are missing from the CMake build. Make this 496 # check an error when the missing files are fixed. 497 # raise PresubmitFailure 498 499 500def build_env_setup(ctx: PresubmitContext): 501 if 'PW_CARGO_SETUP' not in os.environ: 502 _LOG.warning( 503 'Skipping build_env_setup since PW_CARGO_SETUP is not set') 504 return 505 506 tmpl = ctx.root.joinpath('pw_env_setup', 'py', 'pyoxidizer.bzl.tmpl') 507 out = ctx.output_dir.joinpath('pyoxidizer.bzl') 508 509 with open(tmpl, 'r') as ins: 510 cfg = ins.read().replace('${PW_ROOT}', str(ctx.root)) 511 with open(out, 'w') as outs: 512 outs.write(cfg) 513 514 call('pyoxidizer', 'build', cwd=ctx.output_dir) 515 516 517def commit_message_format(_: PresubmitContext): 518 """Checks that the top commit's message is correctly formatted.""" 519 lines = git_repo.commit_message().splitlines() 520 521 # Show limits and current commit message in log. 522 _LOG.debug('%-25s%+25s%+22s', 'Line limits', '72|', '72|') 523 for line in lines: 524 _LOG.debug(line) 525 526 # Ignore Gerrit-generated reverts. 527 if ('Revert' in lines[0] 528 and 'This reverts commit ' in git_repo.commit_message() 529 and 'Reason for revert: ' in git_repo.commit_message()): 530 _LOG.warning('Ignoring apparent Gerrit-generated revert') 531 return 532 533 if not lines: 534 _LOG.error('The commit message is too short!') 535 raise PresubmitFailure 536 537 errors = 0 538 539 if len(lines[0]) > 72: 540 _LOG.warning("The commit message's first line must be no longer than " 541 '72 characters.') 542 _LOG.warning('The first line is %d characters:\n %s', len(lines[0]), 543 lines[0]) 544 errors += 1 545 546 if lines[0].endswith('.'): 547 _LOG.warning( 548 "The commit message's first line must not end with a period:\n %s", 549 lines[0]) 550 errors += 1 551 552 if len(lines) > 1 and lines[1]: 553 _LOG.warning("The commit message's second line must be blank.") 554 _LOG.warning('The second line has %d characters:\n %s', len(lines[1]), 555 lines[1]) 556 errors += 1 557 558 # Check that the lines are 72 characters or less, but skip any lines that 559 # might possibly have a URL, path, or metadata in them. Also skip any lines 560 # with non-ASCII characters. 561 for i, line in enumerate(lines[2:], 3): 562 if any(c in line for c in ':/>') or not line.isascii(): 563 continue 564 565 if len(line) > 72: 566 _LOG.warning( 567 'Commit message lines must be no longer than 72 characters.') 568 _LOG.warning('Line %d has %d characters:\n %s', i, len(line), 569 line) 570 errors += 1 571 572 if errors: 573 _LOG.error('Found %s in the commit message', plural(errors, 'error')) 574 raise PresubmitFailure 575 576 577def static_analysis(ctx: PresubmitContext): 578 """Check that files pass static analyzer checks.""" 579 build.gn_gen(ctx.root, ctx.output_dir, 580 '--export-compile-commands=host_clang_debug') 581 build.ninja(ctx.output_dir, 'host_clang_debug') 582 583 compile_commands = ctx.output_dir.joinpath('compile_commands.json') 584 analyzer_output = ctx.output_dir.joinpath('analyze-build-output') 585 586 if analyzer_output.exists(): 587 shutil.rmtree(analyzer_output) 588 589 call('analyze-build', 590 '--cdb', 591 compile_commands, 592 '--exclude', 593 'third_party', 594 '--output', 595 analyzer_output, 596 cwd=ctx.root, 597 env=build.env_with_clang_vars()) 598 599 # Search for reports under output directory. 600 reports = list(analyzer_output.glob('*/report*')) 601 if len(reports) != 0: 602 archive = shutil.make_archive(str(analyzer_output), 'zip', 603 reports[0].parent) 604 _LOG.error('Static analyzer found errors: %s', archive) 605 _LOG.error('To view report, open: %s', 606 Path(reports[0]).parent.joinpath('index.html')) 607 raise PresubmitFailure 608 609 610def renode_check(ctx: PresubmitContext): 611 """Placeholder for future check.""" 612 _LOG.info('%s %s', ctx.root, ctx.output_dir) 613 614 615# 616# Presubmit check programs 617# 618 619OTHER_CHECKS = ( 620 # TODO(pwbug/45): Remove clang-tidy from OTHER_CHECKS when it passes. 621 clang_tidy, 622 # Build that attempts to duplicate the build OSS-Fuzz does. Currently 623 # failing. 624 oss_fuzz_build, 625 bazel_test, 626 cmake_tests, 627 gn_nanopb_build, 628 gn_full_build_check, 629 gn_full_qemu_check, 630 gn_clang_build, 631 gn_gcc_build, 632 renode_check, 633 static_analysis, 634) 635 636LINTFORMAT = ( 637 commit_message_format, 638 copyright_notice, 639 format_code.presubmit_checks(), 640 pw_presubmit.pragma_once, 641 source_is_in_build_files, 642) 643 644QUICK = ( 645 LINTFORMAT, 646 bazel_test, 647 gn_quick_build_check, 648 # TODO(pwbug/141): Re-enable CMake and Bazel for Mac after we have fixed the 649 # the clang issues. The problem is that all clang++ invocations need the 650 # two extra flags: "-nostdc++" and "${clang_prefix}/../lib/libc++.a". 651 cmake_tests if sys.platform != 'darwin' else (), 652) 653 654FULL = ( 655 LINTFORMAT, 656 gn_host_build, 657 gn_arm_build, 658 gn_docs_build, 659 gn_host_tools, 660 bazel_build, 661 bazel_test, 662 # On Mac OS, system 'gcc' is a symlink to 'clang' by default, so skip GCC 663 # host builds on Mac for now. Skip it on Windows too, since gn_host_build 664 # already uses 'gcc' on Windows. 665 gn_gcc_build if sys.platform not in ('darwin', 'win32') else (), 666 # Windows doesn't support QEMU yet. 667 gn_qemu_build if sys.platform != 'win32' else (), 668 gn_qemu_clang_build if sys.platform != 'win32' else (), 669 source_is_in_build_files, 670 python_checks, 671 build_env_setup, 672 # Skip gn_teensy_build if running on Windows. The Teensycore installer is 673 # an exe that requires an admin role. 674 gn_teensy_build if sys.platform in ['linux', 'darwin'] else (), 675) 676 677PROGRAMS = Programs( 678 full=FULL, 679 lintformat=LINTFORMAT, 680 other_checks=OTHER_CHECKS, 681 quick=QUICK, 682) 683 684 685def parse_args() -> argparse.Namespace: 686 """Creates an argument parser and parses arguments.""" 687 688 parser = argparse.ArgumentParser(description=__doc__) 689 cli.add_arguments(parser, PROGRAMS, 'quick') 690 parser.add_argument( 691 '--install', 692 action='store_true', 693 help='Install the presubmit as a Git pre-push hook and exit.') 694 695 return parser.parse_args() 696 697 698def run(install: bool, **presubmit_args) -> int: 699 """Entry point for presubmit.""" 700 701 if install: 702 install_hook(__file__, 'pre-push', 703 ['--base', 'origin/master..HEAD', '--program', 'quick'], 704 Path.cwd()) 705 return 0 706 707 return cli.run(**presubmit_args) 708 709 710def main() -> int: 711 """Run the presubmit for the Pigweed repository.""" 712 return run(**vars(parse_args())) 713 714 715if __name__ == '__main__': 716 try: 717 # If pw_cli is available, use it to initialize logs. 718 from pw_cli import log 719 720 log.install(logging.INFO) 721 except ImportError: 722 # If pw_cli isn't available, display log messages like a simple print. 723 logging.basicConfig(format='%(message)s', level=logging.INFO) 724 725 sys.exit(main()) 726