blob: 0f8fe4d561ab343cb5cbe8286bec5f6cf9cab8a3 [file] [log] [blame]
# Copyright (C) 2023 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Runs necessary checkpatch targets for a build on ci.android.com.
usage:
tools/bazel run //build/kernel/static_analysis:checkpatch_presubmit -- \\
--dist_dir <DIST_DIR> \\
[<other flags to checkpatch>] \\
"""
import argparse
import collections
import logging
import os
import pathlib
import shlex
import subprocess
import sys
from typing import Any
_LOG_LEVEL = logging.INFO
# _LOG_LEVEL = logging.DEBUG
_BAZEL = pathlib.Path("tools/bazel")
_SILENT_ARGS = [
"--ui_event_filters=-info",
"--noshow_progress",
]
# TODO: Find a better way to handle this exceptions;
_PATH_PREFIX_DENY_LIST = (
"external/",
)
def load_arguments() -> dict[str, Any]:
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument(
"--dist_dir",
type=_resolve_against_workspace_root,
required=True,
help="DIST_DIR. If relative, resolve against workspace root.",
)
parser.add_argument(
"--bid",
help="Build ID. If specified, it is used to skip the check on post-submit.",
)
return parser.parse_known_args()
def _resolve_against_workspace_root(value: str) -> pathlib.Path:
path = pathlib.Path(value)
if path.is_absolute():
return path
return pathlib.Path(os.environ["BUILD_WORKSPACE_DIRECTORY"]) / path
def _log_command(args):
quoted = [shlex.quote(str(arg)) for arg in args]
logging.debug("Running command line: %s", " ".join(quoted))
def _find_checkpatch_targets(path: pathlib.Path) -> list[str]:
if str(path).startswith(_PATH_PREFIX_DENY_LIST):
logging.info("Skipped //%s path in deny list", path)
return []
if not _resolve_against_workspace_root(path / "BUILD.bazel").is_file() and \
not _resolve_against_workspace_root(path / "BUILD").is_file():
logging.info("//%s is not a package; no BUILD file is found", path)
return []
args = [_BAZEL, "query"]
args += _SILENT_ARGS
args.append(f'kind("^checkpatch rule$", //{path}:all)')
_log_command(args)
lines = subprocess.check_output(
args,
text=True,
cwd=_resolve_against_workspace_root("."),
).splitlines()
return [line.strip() for line in lines if line.strip()]
def _run_checkpatch(
target: str,
git_sha1: str,
log: pathlib.Path,
checkpatch_args: list[str],
silent: bool = False,
) -> int:
args = [_BAZEL, "run", "--show_result=0"]
args += _SILENT_ARGS
args += [target, "--"]
args += checkpatch_args
args += ["--log", log]
args += ["--git_sha1", git_sha1]
_log_command(args)
return subprocess.run(
args,
text=True,
cwd=_resolve_against_workspace_root("."),
stdout=subprocess.DEVNULL if silent else None,
stderr=subprocess.STDOUT if silent else None,
).returncode
def main(
checkpatch_args: list[str],
dist_dir: pathlib.Path,
bid: str | None,
) -> int:
if bid:
# Skip checkpatch for postsubmit (b/35390488).
if not bid.startswith("P"):
logging.info("Did not identify a presubmit build. Exiting.")
return 0
applied_prop = dist_dir / "applied.prop"
applied_prop_dict: dict[pathlib.Path, list[str]] = \
collections.defaultdict(list)
with open(applied_prop) as applied_prop_file:
for line in applied_prop_file:
line = line.strip()
if not line:
continue
path, git_sha1 = line.split(maxsplit=2)
applied_prop_dict[pathlib.Path(path)].append(git_sha1)
targets: list[(list[str], str)] = []
for path, git_sha1_list in applied_prop_dict.items():
if len(git_sha1_list) > 1:
logging.error("Multiple git sha1 found in %s for %s",
applied_prop, path)
return 1
path_targets = _find_checkpatch_targets(path)
if not path_targets:
logging.info(
"Skipping %s because no checkpatch() target is found.", path)
continue
targets.append((path_targets, git_sha1_list[0]))
checkpatch_log = dist_dir / "checkpatch.log"
checkpatch_full_log = dist_dir / "checkpatch_full.log"
if checkpatch_log.exists():
os.unlink(checkpatch_log)
if checkpatch_full_log.exists():
os.unlink(checkpatch_full_log)
return_codes = []
for path_targets, git_sha1 in targets:
for target in path_targets:
return_codes.append(_run_checkpatch(
target=target,
git_sha1=git_sha1,
log=checkpatch_log,
checkpatch_args=checkpatch_args,
))
_run_checkpatch(
target=target,
git_sha1=git_sha1,
log=checkpatch_full_log,
checkpatch_args=checkpatch_args + ["--ignored_checks", ""],
silent=True,
)
success = sum(return_codes) == 0
if not success:
logging.info("See %s for complete output.", checkpatch_log.name)
return 0 if success else 1
if __name__ == "__main__":
logging.basicConfig(level=_LOG_LEVEL,
format="%(levelname)s: %(message)s")
known, checkpatch_args = load_arguments()
sys.exit(main(checkpatch_args=checkpatch_args, **vars(known)))