2022-08-11 18:28:03 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# Copyright 2022 The Chromium OS Authors. All rights reserved.
|
|
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
|
|
# found in the LICENSE file.
|
|
|
|
|
2022-08-16 19:57:01 +00:00
|
|
|
from dataclasses import dataclass
|
2022-08-11 18:28:03 +00:00
|
|
|
from fnmatch import fnmatch
|
2022-08-16 19:57:01 +00:00
|
|
|
from pathlib import Path
|
2022-08-11 18:28:03 +00:00
|
|
|
from time import time
|
|
|
|
from typing import Callable, List, NamedTuple
|
|
|
|
|
2022-08-16 19:57:01 +00:00
|
|
|
from impl.common import all_tracked_files, cmd, verbose
|
2022-08-11 18:28:03 +00:00
|
|
|
|
|
|
|
git = cmd("git")
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class CheckContext(object):
|
|
|
|
"Information passed to each check when it's called."
|
|
|
|
|
|
|
|
# Whether or not --fix was set and checks should attempt to fix problems they encounter.
|
|
|
|
fix: bool
|
|
|
|
|
|
|
|
# Use rust nightly version for rust checks
|
|
|
|
nightly: bool
|
|
|
|
|
|
|
|
# All files that this check should cover (e.g. all python files on a python check).
|
|
|
|
all_files: List[Path]
|
|
|
|
|
|
|
|
# Those files of all_files that were modified locally.
|
|
|
|
modified_files: List[Path]
|
|
|
|
|
2022-08-16 19:57:01 +00:00
|
|
|
# Files that do not exist upstream and have been added locally.
|
|
|
|
new_files: List[Path]
|
|
|
|
|
2022-08-11 18:28:03 +00:00
|
|
|
|
|
|
|
class Check(NamedTuple):
|
|
|
|
"Metadata for each check, definining on which files it should run."
|
|
|
|
|
|
|
|
# Function to call for this check
|
|
|
|
check_function: Callable[[CheckContext], None]
|
|
|
|
|
|
|
|
# List of globs that this check should be triggered on
|
|
|
|
files: List[str] = []
|
|
|
|
|
|
|
|
python_tools: bool = False
|
|
|
|
|
|
|
|
# List of globs to exclude from this check
|
|
|
|
exclude: List[str] = []
|
|
|
|
|
2022-08-16 17:58:38 +00:00
|
|
|
# Whether or not this check can fix issues.
|
|
|
|
can_fix: bool = False
|
|
|
|
|
2022-08-11 18:28:03 +00:00
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
name = self.check_function.__name__
|
|
|
|
if name.startswith("check_"):
|
|
|
|
return name[len("check_") :]
|
|
|
|
return name
|
|
|
|
|
|
|
|
|
2022-08-16 19:57:01 +00:00
|
|
|
def list_file_diff():
|
2022-08-11 18:28:03 +00:00
|
|
|
"""
|
|
|
|
Lists files there were modified compared to the upstream branch.
|
|
|
|
|
|
|
|
Falls back to all files tracked by git if there is no upstream branch.
|
|
|
|
"""
|
|
|
|
upstream = git("rev-parse @{u}").stdout(check=False)
|
|
|
|
if upstream:
|
2022-08-16 19:57:01 +00:00
|
|
|
for line in git("diff --name-status", upstream).lines():
|
|
|
|
parts = line.split("\t", 1)
|
2022-08-17 19:59:27 +00:00
|
|
|
file = Path(parts[1].strip())
|
|
|
|
if file.is_file():
|
|
|
|
yield (parts[0].strip(), file)
|
2022-08-11 18:28:03 +00:00
|
|
|
else:
|
|
|
|
print("WARNING: Not tracking a branch. Checking all files.")
|
2022-08-16 19:57:01 +00:00
|
|
|
for file in all_tracked_files():
|
|
|
|
yield ("M", file)
|
2022-08-11 18:28:03 +00:00
|
|
|
|
|
|
|
|
|
|
|
def should_run_check_on_file(check: Check, file: Path):
|
|
|
|
"Returns true if `file` should be run on `check`."
|
|
|
|
|
2022-08-26 17:47:11 +00:00
|
|
|
# Skip third_party except vmm_vhost.
|
|
|
|
if str(file).startswith("third_party") and not str(file).startswith("third_party/vmm_vhost"):
|
2022-08-11 18:28:03 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
# Skip excluded files
|
|
|
|
for glob in check.exclude:
|
|
|
|
if fnmatch(str(file), glob):
|
|
|
|
return False
|
|
|
|
|
|
|
|
# Match python tools (no file-extension, but with a python shebang line)
|
|
|
|
if check.python_tools:
|
|
|
|
if fnmatch(str(file), "tools/*") and file.suffix == "" and file.is_file():
|
|
|
|
if file.open(errors="ignore").read(32).startswith("#!/usr/bin/env python3"):
|
|
|
|
return True
|
|
|
|
|
|
|
|
# If no constraint is specified, match all files.
|
|
|
|
if not check.files and not check.python_tools:
|
|
|
|
return True
|
|
|
|
|
|
|
|
# Otherwise, match only those specified by `files`.
|
|
|
|
for glob in check.files:
|
|
|
|
if fnmatch(str(file), glob):
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def run_check(check: Check, context: CheckContext):
|
|
|
|
"Runs `check` using the information in `context`. Prints status updates."
|
|
|
|
start_time = time()
|
|
|
|
if verbose():
|
|
|
|
print(f"Checking {check.name}...")
|
|
|
|
try:
|
|
|
|
check.check_function(context)
|
|
|
|
success = True
|
|
|
|
except Exception as e:
|
|
|
|
print(e)
|
|
|
|
success = False
|
|
|
|
|
|
|
|
duration = time() - start_time
|
|
|
|
print(f"Check {check.name}", "OK" if success else "FAILED", f" ({duration:.2f} s)")
|
|
|
|
return success
|
|
|
|
|
|
|
|
|
|
|
|
def run_checks(
|
|
|
|
checks_list: List[Check],
|
|
|
|
fix: bool,
|
|
|
|
run_on_all_files: bool,
|
|
|
|
nightly: bool,
|
|
|
|
):
|
|
|
|
"""
|
|
|
|
Runs all checks in checks_list.
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
fix: Tell checks to fix issues if they can (e.g. run formatter).
|
|
|
|
run_on_all_files: Do not use git delta, but run on all files.
|
|
|
|
nightly: Use nightly version of rust tooling.
|
|
|
|
"""
|
|
|
|
all_files = [*all_tracked_files()]
|
2022-08-16 19:57:01 +00:00
|
|
|
file_diff = [*list_file_diff()]
|
|
|
|
new_files = [f for (s, f) in file_diff if s == "A"]
|
2022-08-11 18:28:03 +00:00
|
|
|
if run_on_all_files:
|
|
|
|
modified_files = all_files
|
|
|
|
else:
|
2022-08-16 19:57:01 +00:00
|
|
|
modified_files = [f for (s, f) in file_diff if s in ("M", "A")]
|
2022-08-11 18:28:03 +00:00
|
|
|
|
2022-08-16 17:58:38 +00:00
|
|
|
failed_checks: List[Check] = []
|
2022-08-11 18:28:03 +00:00
|
|
|
for check in checks_list:
|
|
|
|
context = CheckContext(
|
|
|
|
fix=fix,
|
|
|
|
nightly=nightly,
|
|
|
|
all_files=[f for f in all_files if should_run_check_on_file(check, f)],
|
|
|
|
modified_files=[f for f in modified_files if should_run_check_on_file(check, f)],
|
2022-08-16 19:57:01 +00:00
|
|
|
new_files=[f for f in new_files if should_run_check_on_file(check, f)],
|
2022-08-11 18:28:03 +00:00
|
|
|
)
|
|
|
|
if context.modified_files:
|
|
|
|
if not run_check(check, context):
|
2022-08-16 17:58:38 +00:00
|
|
|
failed_checks.append(check)
|
|
|
|
if any(c.can_fix for c in failed_checks):
|
|
|
|
print("")
|
|
|
|
print("Some of the issues above can be fixed automatically with:")
|
|
|
|
print("./tools/health-check --fix")
|
|
|
|
return len(failed_checks) == 0
|