1# Copyright 2023 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://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, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14"""Presubmit script.""" 15 16import argparse 17import logging 18import os 19from pathlib import Path 20import re 21import sys 22 23import pw_cli.log 24import pw_presubmit 25from pw_presubmit import ( 26 build, 27 cli, 28 cpp_checks, 29 format_code, 30 git_repo, 31 inclusive_language, 32 install_hook, 33 keep_sorted, 34 presubmit_context, 35 python_checks, 36) 37 38_LOG = logging.getLogger(__name__) 39 40# Set up variables for key project paths. 41try: 42 PROJECT_ROOT = Path(os.environ["PW_PROJECT_ROOT"]) 43except KeyError: 44 print( 45 "ERROR: The presubmit checks must be run in the Open Dice project's " 46 "root directory", 47 file=sys.stderr, 48 ) 49 sys.exit(2) 50 51PIGWEED_ROOT = PROJECT_ROOT / "third_party" / "pigweed" / "src" 52 53# Rerun the build if files with these extensions change. 54_BUILD_EXTENSIONS = frozenset( 55 [".rst", ".gn", ".gni", *format_code.C_FORMAT.extensions] 56) 57 58default_build = build.GnGenNinja(name="default_build") 59 60EXCLUSIONS = presubmit_context.FormatOptions.load().exclude 61 62OTHER_CHECKS = (build.gn_gen_check,) 63 64_FORMAT = (format_code.presubmit_checks(exclude=EXCLUSIONS),) 65 66QUICK = ( 67 default_build, 68 _FORMAT, 69) 70 71 72def include_guard(path: Path) -> str: 73 path = path.relative_to(PROJECT_ROOT) 74 exclude = ("include",) 75 transform = {"ulib": "lib"} 76 parts = [transform.get(x, x) for x in path.parts if x not in exclude] 77 return re.sub(r"[.-]", "_", "".join(f"{x}_" for x in parts).upper()) 78 79 80LINTFORMAT = ( 81 _FORMAT, 82 cpp_checks.include_guard_check(include_guard).with_filter( 83 exclude=EXCLUSIONS 84 ), 85 inclusive_language.presubmit_check.with_filter(exclude=EXCLUSIONS), 86 keep_sorted.presubmit_check.with_filter(exclude=EXCLUSIONS), 87 python_checks.gn_python_lint, 88) 89 90FULL = ( 91 QUICK, # Add all checks from the 'quick' program 92 LINTFORMAT, 93 # Use the upstream Python checks, with custom path filters applied. 94 python_checks.gn_python_check, 95) 96 97PROGRAMS = pw_presubmit.Programs( 98 # keep-sorted: start 99 full=FULL, 100 lintformat=LINTFORMAT, 101 other_checks=OTHER_CHECKS, 102 quick=QUICK, 103 # keep-sorted: end 104) 105 106 107def run(install: bool, exclude: list, **presubmit_args) -> int: 108 """Process the --install argument then invoke pw_presubmit.""" 109 110 # Install the presubmit Git pre-push hook, if requested. 111 if install: 112 install_hook.install_git_hook( 113 "pre-push", 114 [ 115 "python", 116 "-m", 117 "open_dice_tools.presubmit", 118 "--base", 119 "origin/main..HEAD", 120 "--program", 121 "quick", 122 ], 123 ) 124 return 0 125 126 repos = git_repo.discover_submodules(superproject_dir=PROJECT_ROOT) 127 return cli.run( 128 root=PROJECT_ROOT, repositories=repos, exclude=exclude, **presubmit_args 129 ) 130 131 132def main() -> int: 133 """Run the presubmit checks for this repository.""" 134 parser = argparse.ArgumentParser(description=__doc__) 135 cli.add_arguments(parser, PROGRAMS, "quick") 136 137 # Define an option for installing a Git pre-push hook for this script. 138 parser.add_argument( 139 "--install", 140 action="store_true", 141 help="Install the presubmit as a Git pre-push hook and exit.", 142 ) 143 144 return run(**vars(parser.parse_args())) 145 146 147if __name__ == "__main__": 148 pw_cli.log.install(logging.INFO) 149 sys.exit(main()) 150