mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-09 03:57:24 +00:00
On glinux people may have set up the google internal mdformat tool, which is different from the open source mdformat tool. BUG=b:236962138 TEST=./tools/health-check --all markdown_format with PATH updated to use both versions Change-Id: Ia6b783c52195f2edd33eb836333b28f257be8a32 Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3894236 Reviewed-by: Zihan Chen <zihanchen@google.com> Commit-Queue: Dennis Kempin <denniskempin@google.com>
302 lines
9.1 KiB
Python
Executable file
302 lines
9.1 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# Copyright 2022 The ChromiumOS Authors
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import List
|
|
|
|
from impl.check_code_hygiene import has_crlf_line_endings
|
|
from impl.common import (
|
|
CROSVM_ROOT,
|
|
TOOLS_ROOT,
|
|
argh,
|
|
chdir,
|
|
cmd,
|
|
cwd_context,
|
|
parallel,
|
|
run_main,
|
|
)
|
|
from impl.health_check import Check, CheckContext, run_checks
|
|
|
|
python = cmd("python3")
|
|
mypy = cmd("mypy").with_color_env("MYPY_FORCE_COLOR")
|
|
black = cmd("black").with_color_arg(always="--color", never="--no-color")
|
|
mdformat = cmd("mdformat")
|
|
lucicfg = cmd("third_party/depot_tools/lucicfg")
|
|
|
|
|
|
def check_python_tests(_: CheckContext):
|
|
"No matter which python files have changed, run all available python tests."
|
|
PYTHON_TESTS = [
|
|
*TOOLS_ROOT.glob("tests/*.py"),
|
|
TOOLS_ROOT / "impl/common.py",
|
|
]
|
|
parallel(*cmd("python3").foreach(PYTHON_TESTS)).fg(quiet=True)
|
|
|
|
|
|
def check_python_types(context: CheckContext):
|
|
"Run mypy on all python files to type-check."
|
|
parallel(*mypy("--pretty").foreach(context.all_files)).fg(quiet=True)
|
|
|
|
|
|
def check_python_format(context: CheckContext):
|
|
parallel(*black("--check" if not context.fix else None).foreach(context.modified_files)).fg(
|
|
quiet=not context.fix
|
|
)
|
|
|
|
|
|
def check_crlf_line_endings(_: CheckContext):
|
|
"Checks for crlf line endingings."
|
|
crlf_endings = has_crlf_line_endings()
|
|
if crlf_endings:
|
|
print("Error: Following files have crlf(dos) line encodings")
|
|
print(*crlf_endings)
|
|
raise Exception("Files with crlf line endings.")
|
|
|
|
|
|
def check_markdown_format(context: CheckContext):
|
|
"Runs mdformat on all markdown files."
|
|
if "blaze" in mdformat("--version").stdout():
|
|
raise Exception(
|
|
"You are using google's mdformat. "
|
|
+ "Please update your PATH to ensure the pip installed mdformat is available."
|
|
)
|
|
parallel(
|
|
*mdformat("--wrap 100", "--check" if not context.fix else "").foreach(
|
|
context.modified_files
|
|
)
|
|
).fg(quiet=not context.fix)
|
|
|
|
|
|
def check_rust_clippy(_: CheckContext):
|
|
"Runs clippy on the whole project, no matter which rs files were touched."
|
|
cmd("./tools/clippy --locked").with_color_flag().fg(quiet=True)
|
|
|
|
|
|
def check_rust_format(context: CheckContext):
|
|
"Runs rustfmt on all modified files."
|
|
if context.nightly:
|
|
rustfmt = cmd(
|
|
cmd("rustup +nightly which rustfmt"),
|
|
"--config imports_granularity=item,group_imports=StdExternalCrate",
|
|
).with_color_flag()
|
|
else:
|
|
rustfmt = cmd(cmd("rustup which rustfmt"))
|
|
parallel(
|
|
*rustfmt("--check" if not context.fix else "")
|
|
.with_color_flag()
|
|
.foreach(context.modified_files)
|
|
).fg(quiet=not context.fix)
|
|
|
|
|
|
def check_rust_lockfiles(_: CheckContext):
|
|
"Verifies that none of the Cargo.lock files require updates."
|
|
lockfiles = [Path("Cargo.lock"), *Path("common").glob("*/Cargo.lock")]
|
|
for path in lockfiles:
|
|
with cwd_context(path.parent):
|
|
if not cmd("cargo update --workspace --locked").success():
|
|
print(f"{path} is not up-to-date.")
|
|
print()
|
|
print("You may need to rebase your changes and run `cargo update --workspace`")
|
|
print("(or ./tools/run_tests) to ensure the Cargo.lock file is current.")
|
|
raise Exception("Cargo.lock out of date")
|
|
|
|
|
|
LICENSE_HEADER_RE = (
|
|
r".*Copyright (?P<year>20[0-9]{2})(?:-20[0-9]{2})? The ChromiumOS Authors\n"
|
|
r".*Use of this source code is governed by a BSD-style license that can be\n"
|
|
r".*found in the LICENSE file\.\n"
|
|
)
|
|
|
|
NEW_LICENSE_HEADER = [
|
|
f"Copyright {datetime.now().year} The ChromiumOS Authors",
|
|
"Use of this source code is governed by a BSD-style license that can be",
|
|
"found in the LICENSE file.",
|
|
]
|
|
|
|
|
|
def new_licence_header(file_suffix: str):
|
|
if file_suffix in (".py", "", ".policy", ".sh"):
|
|
prefix = "#"
|
|
else:
|
|
prefix = "//"
|
|
return "\n".join(f"{prefix} {line}" for line in NEW_LICENSE_HEADER) + "\n\n"
|
|
|
|
|
|
def check_copyright_header(context: CheckContext):
|
|
"Checks copyright header. Can 'fix' them if needed by adding the header."
|
|
license_re = re.compile(LICENSE_HEADER_RE, re.MULTILINE)
|
|
for file in context.modified_files:
|
|
header = file.open("r").read(512)
|
|
license_match = license_re.search(header)
|
|
if license_match:
|
|
continue
|
|
# Generated files do not need a copyright header.
|
|
if "generated by" in header:
|
|
continue
|
|
if context.fix:
|
|
print(f"Adding copyright header: {file}")
|
|
contents = file.read_text()
|
|
file.write_text(new_licence_header(file.suffix) + contents)
|
|
else:
|
|
raise Exception(f"Bad copyright header: {file}")
|
|
|
|
|
|
def check_infra_configs(context: CheckContext):
|
|
"Validate luci configs by sending them to luci-config."
|
|
for file in context.modified_files:
|
|
if context.fix:
|
|
lucicfg("fmt", file).fg()
|
|
lucicfg("generate", file).fg()
|
|
lucicfg("fmt --dry-run", file).fg(quiet=True)
|
|
# TODO: Validate config files. Requires authentication with luci inside docker.
|
|
|
|
|
|
def check_infra_tests(context: CheckContext):
|
|
"Run recipe.py tests, all of them, regardless of which files were modified."
|
|
recipes = cmd("infra/recipes.py").with_path_env("third_party/depot_tools")
|
|
if context.fix:
|
|
recipes("test train --py3-only").fg()
|
|
recipes("test run --py3-only").fg(quiet=True)
|
|
|
|
|
|
def check_file_ends_with_newline(context: CheckContext):
|
|
"Checks if files end with a newline."
|
|
for file_path in context.modified_files:
|
|
with file_path.open("rb") as file:
|
|
# Skip empty files
|
|
file.seek(0, os.SEEK_END)
|
|
if file.tell() == 0:
|
|
continue
|
|
# Check last byte of the file
|
|
file.seek(-1, os.SEEK_END)
|
|
file_end = file.read(1)
|
|
if file_end.decode("utf-8") != "\n":
|
|
if context.fix:
|
|
file_path.write_text(file_path.read_text() + "\n")
|
|
else:
|
|
raise Exception(f"File does not end with a newline {file_path}")
|
|
|
|
|
|
# List of all checks and on which files they should run.
|
|
CHECKS: List[Check] = [
|
|
Check(
|
|
check_copyright_header,
|
|
files=["**.rs", "**.py", "**.c", "**.h", "**.policy", "**.sh"],
|
|
exclude=[
|
|
"infra/recipes.py",
|
|
"hypervisor/src/whpx/whpx_sys/*.h",
|
|
"third_party/vmm_vhost/*",
|
|
"net_sys/src/lib.rs",
|
|
],
|
|
python_tools=True,
|
|
),
|
|
Check(
|
|
check_rust_format,
|
|
files=["**.rs"],
|
|
can_fix=True,
|
|
),
|
|
Check(
|
|
check_rust_lockfiles,
|
|
files=["**Cargo.toml"],
|
|
),
|
|
Check(
|
|
check_rust_clippy,
|
|
files=["**.rs", "**Cargo.toml"],
|
|
),
|
|
Check(
|
|
check_python_tests,
|
|
files=["tools/**.py"],
|
|
python_tools=True,
|
|
),
|
|
Check(
|
|
check_python_types,
|
|
files=["tools/**.py"],
|
|
exclude=["tools/windows/*"],
|
|
python_tools=True,
|
|
),
|
|
Check(
|
|
check_python_format,
|
|
files=["**.py"],
|
|
python_tools=True,
|
|
exclude=["infra/recipes.py"],
|
|
can_fix=True,
|
|
),
|
|
Check(
|
|
check_infra_configs,
|
|
files=["infra/config/**.star"],
|
|
can_fix=True,
|
|
),
|
|
Check(
|
|
check_infra_tests,
|
|
files=["infra/**.py"],
|
|
can_fix=True,
|
|
),
|
|
Check(
|
|
check_markdown_format,
|
|
files=["**.md"],
|
|
exclude=[
|
|
"infra/README.recipes.md",
|
|
"docs/book/src/appendix/memory_layout.md",
|
|
],
|
|
can_fix=True,
|
|
),
|
|
Check(
|
|
check_file_ends_with_newline,
|
|
exclude=[
|
|
"**.h264",
|
|
"**.vp8",
|
|
"**.bin",
|
|
"**.png",
|
|
"**.min.js",
|
|
"**.drawio",
|
|
"infra/**.json",
|
|
],
|
|
),
|
|
Check(check_crlf_line_endings),
|
|
]
|
|
|
|
CHECKS_DICT = dict((c.name, c) for c in CHECKS)
|
|
|
|
|
|
@argh.arg("--list-checks", default=False, help="List names of available checks and exit.")
|
|
@argh.arg("--fix", default=False, help="Asks checks to fix problems where possible.")
|
|
@argh.arg("--all", default=False, help="Run on all files instead of just modified files.")
|
|
@argh.arg(
|
|
"checks",
|
|
choices=[*CHECKS_DICT.keys(), []],
|
|
help="Optional list of checks to run. Defaults to run all checks.",
|
|
)
|
|
def main(
|
|
list_checks: bool = False,
|
|
fix: bool = False,
|
|
all: bool = False,
|
|
nightly: bool = False,
|
|
*checks: str,
|
|
):
|
|
"""
|
|
Run health checks on crosvm. This includes formatting, linters and other various checks.
|
|
"""
|
|
chdir(CROSVM_ROOT)
|
|
|
|
if not checks:
|
|
checks_list = [*CHECKS_DICT.values()]
|
|
else:
|
|
checks_list = [CHECKS_DICT[check] for check in checks]
|
|
|
|
if list_checks:
|
|
for check in checks_list:
|
|
print(check.name)
|
|
return
|
|
success = run_checks(checks_list, fix=fix, run_on_all_files=all, nightly=nightly)
|
|
|
|
sys.exit(0 if success else 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run_main(main)
|