xref: /aosp_15_r20/external/crosvm/tools/custom_checks (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1*bb4ee6a4SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*bb4ee6a4SAndroid Build Coastguard Worker# Copyright 2022 The ChromiumOS Authors
3*bb4ee6a4SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
4*bb4ee6a4SAndroid Build Coastguard Worker# found in the LICENSE file.
5*bb4ee6a4SAndroid Build Coastguard Worker
6*bb4ee6a4SAndroid Build Coastguard Worker# Contains custom presubmit checks implemented in python.
7*bb4ee6a4SAndroid Build Coastguard Worker#
8*bb4ee6a4SAndroid Build Coastguard Worker# These are implemented as a separate CLI tool from tools/presubmit as the presubmit
9*bb4ee6a4SAndroid Build Coastguard Worker# framework needs to call a subprocess to execute checks.
10*bb4ee6a4SAndroid Build Coastguard Worker
11*bb4ee6a4SAndroid Build Coastguard Workerfrom fnmatch import fnmatch
12*bb4ee6a4SAndroid Build Coastguard Workerimport os
13*bb4ee6a4SAndroid Build Coastguard Workerimport re
14*bb4ee6a4SAndroid Build Coastguard Workerimport json
15*bb4ee6a4SAndroid Build Coastguard Workerfrom datetime import datetime
16*bb4ee6a4SAndroid Build Coastguard Workerfrom pathlib import Path
17*bb4ee6a4SAndroid Build Coastguard Workerfrom typing import Dict, Generator, List, cast
18*bb4ee6a4SAndroid Build Coastguard Worker
19*bb4ee6a4SAndroid Build Coastguard Workerfrom impl.common import (
20*bb4ee6a4SAndroid Build Coastguard Worker    cmd,
21*bb4ee6a4SAndroid Build Coastguard Worker    cwd_context,
22*bb4ee6a4SAndroid Build Coastguard Worker    run_commands,
23*bb4ee6a4SAndroid Build Coastguard Worker)
24*bb4ee6a4SAndroid Build Coastguard Worker
25*bb4ee6a4SAndroid Build Coastguard Worker
26*bb4ee6a4SAndroid Build Coastguard Workerdef check_platform_independent(*files: str):
27*bb4ee6a4SAndroid Build Coastguard Worker    "Checks the provided files to ensure they are free of platform independent code."
28*bb4ee6a4SAndroid Build Coastguard Worker    cfg_unix = "cfg.*unix"
29*bb4ee6a4SAndroid Build Coastguard Worker    cfg_linux = "cfg.*linux"
30*bb4ee6a4SAndroid Build Coastguard Worker    cfg_windows = "cfg.*windows"
31*bb4ee6a4SAndroid Build Coastguard Worker    cfg_android = "cfg.*android"
32*bb4ee6a4SAndroid Build Coastguard Worker    target_os = "target_os = "
33*bb4ee6a4SAndroid Build Coastguard Worker
34*bb4ee6a4SAndroid Build Coastguard Worker    target_os_pattern = re.compile(
35*bb4ee6a4SAndroid Build Coastguard Worker        "%s|%s|%s|%s|%s" % (cfg_android, cfg_linux, cfg_unix, cfg_windows, target_os)
36*bb4ee6a4SAndroid Build Coastguard Worker    )
37*bb4ee6a4SAndroid Build Coastguard Worker
38*bb4ee6a4SAndroid Build Coastguard Worker    for file in files:
39*bb4ee6a4SAndroid Build Coastguard Worker        for line_number, line in enumerate(open(file, encoding="utf8")):
40*bb4ee6a4SAndroid Build Coastguard Worker            if re.search(target_os_pattern, line):
41*bb4ee6a4SAndroid Build Coastguard Worker                raise Exception(f"Found unexpected platform dependent code in {file}:{line_number}")
42*bb4ee6a4SAndroid Build Coastguard Worker
43*bb4ee6a4SAndroid Build Coastguard Worker
44*bb4ee6a4SAndroid Build Coastguard WorkerCRLF_LINE_ENDING_FILES: List[str] = [
45*bb4ee6a4SAndroid Build Coastguard Worker    "**.bat",
46*bb4ee6a4SAndroid Build Coastguard Worker    "**.ps1",
47*bb4ee6a4SAndroid Build Coastguard Worker    "e2e_tests/tests/goldens/backcompat_test_simple_lspci_win.txt",
48*bb4ee6a4SAndroid Build Coastguard Worker    "tools/windows/build_test",
49*bb4ee6a4SAndroid Build Coastguard Worker]
50*bb4ee6a4SAndroid Build Coastguard Worker
51*bb4ee6a4SAndroid Build Coastguard Worker
52*bb4ee6a4SAndroid Build Coastguard Workerdef is_crlf_file(file: str):
53*bb4ee6a4SAndroid Build Coastguard Worker    for glob in CRLF_LINE_ENDING_FILES:
54*bb4ee6a4SAndroid Build Coastguard Worker        if fnmatch(file, glob):
55*bb4ee6a4SAndroid Build Coastguard Worker            return True
56*bb4ee6a4SAndroid Build Coastguard Worker    return False
57*bb4ee6a4SAndroid Build Coastguard Worker
58*bb4ee6a4SAndroid Build Coastguard Worker
59*bb4ee6a4SAndroid Build Coastguard Workerdef check_line_endings(*files: str):
60*bb4ee6a4SAndroid Build Coastguard Worker    "Checks line endings. Windows only files are using clrf. All others just lf."
61*bb4ee6a4SAndroid Build Coastguard Worker    for line in cmd("git ls-files --eol", *files).lines():
62*bb4ee6a4SAndroid Build Coastguard Worker        parts = line.split()
63*bb4ee6a4SAndroid Build Coastguard Worker        file = parts[-1]
64*bb4ee6a4SAndroid Build Coastguard Worker        index_endings = parts[0][2:]
65*bb4ee6a4SAndroid Build Coastguard Worker        wdir_endings = parts[1][2:]
66*bb4ee6a4SAndroid Build Coastguard Worker
67*bb4ee6a4SAndroid Build Coastguard Worker        def check_endings(endings: str):
68*bb4ee6a4SAndroid Build Coastguard Worker            if is_crlf_file(file):
69*bb4ee6a4SAndroid Build Coastguard Worker                if endings not in ("crlf", "mixed"):
70*bb4ee6a4SAndroid Build Coastguard Worker                    raise Exception(f"{file} Expected crlf file endings. Found {endings}")
71*bb4ee6a4SAndroid Build Coastguard Worker            else:
72*bb4ee6a4SAndroid Build Coastguard Worker                if endings in ("crlf", "mixed"):
73*bb4ee6a4SAndroid Build Coastguard Worker                    raise Exception(f"{file} Expected lf file endings. Found {endings}")
74*bb4ee6a4SAndroid Build Coastguard Worker
75*bb4ee6a4SAndroid Build Coastguard Worker        check_endings(index_endings)
76*bb4ee6a4SAndroid Build Coastguard Worker        check_endings(wdir_endings)
77*bb4ee6a4SAndroid Build Coastguard Worker
78*bb4ee6a4SAndroid Build Coastguard Worker
79*bb4ee6a4SAndroid Build Coastguard Workerdef check_rust_lockfiles(*files: str):
80*bb4ee6a4SAndroid Build Coastguard Worker    "Verifies that none of the Cargo.lock files require updates."
81*bb4ee6a4SAndroid Build Coastguard Worker    lockfiles = [Path("Cargo.lock"), *Path("common").glob("*/Cargo.lock")]
82*bb4ee6a4SAndroid Build Coastguard Worker    for path in lockfiles:
83*bb4ee6a4SAndroid Build Coastguard Worker        with cwd_context(path.parent):
84*bb4ee6a4SAndroid Build Coastguard Worker            if not cmd("cargo update --workspace --locked").success():
85*bb4ee6a4SAndroid Build Coastguard Worker                print(f"{path} is not up-to-date.")
86*bb4ee6a4SAndroid Build Coastguard Worker                print()
87*bb4ee6a4SAndroid Build Coastguard Worker                print("You may need to rebase your changes and run `cargo update --workspace`")
88*bb4ee6a4SAndroid Build Coastguard Worker                print("(or ./tools/run_tests) to ensure the Cargo.lock file is current.")
89*bb4ee6a4SAndroid Build Coastguard Worker                raise Exception("Cargo.lock out of date")
90*bb4ee6a4SAndroid Build Coastguard Worker
91*bb4ee6a4SAndroid Build Coastguard Worker
92*bb4ee6a4SAndroid Build Coastguard Worker# These crosvm features are currently not built upstream. Do not add to this list.
93*bb4ee6a4SAndroid Build Coastguard WorkerKNOWN_DISABLED_FEATURES = [
94*bb4ee6a4SAndroid Build Coastguard Worker    "default-no-sandbox",
95*bb4ee6a4SAndroid Build Coastguard Worker    "gvm",
96*bb4ee6a4SAndroid Build Coastguard Worker    "libvda",
97*bb4ee6a4SAndroid Build Coastguard Worker    "perfetto",
98*bb4ee6a4SAndroid Build Coastguard Worker    "process-invariants",
99*bb4ee6a4SAndroid Build Coastguard Worker    "prod-build",
100*bb4ee6a4SAndroid Build Coastguard Worker    "sandbox",
101*bb4ee6a4SAndroid Build Coastguard Worker    "seccomp_trace",
102*bb4ee6a4SAndroid Build Coastguard Worker    "slirp-ring-capture",
103*bb4ee6a4SAndroid Build Coastguard Worker    "vulkano",
104*bb4ee6a4SAndroid Build Coastguard Worker    "whpx",
105*bb4ee6a4SAndroid Build Coastguard Worker]
106*bb4ee6a4SAndroid Build Coastguard Worker
107*bb4ee6a4SAndroid Build Coastguard Worker
108*bb4ee6a4SAndroid Build Coastguard Workerdef check_rust_features(*files: str):
109*bb4ee6a4SAndroid Build Coastguard Worker    "Verifies that all cargo features are included in the list of features we compile upstream."
110*bb4ee6a4SAndroid Build Coastguard Worker    metadata = json.loads(cmd("cargo metadata --format-version=1").stdout())
111*bb4ee6a4SAndroid Build Coastguard Worker    crosvm_metadata = next(p for p in metadata["packages"] if p["name"] == "crosvm")
112*bb4ee6a4SAndroid Build Coastguard Worker    features = cast(Dict[str, List[str]], crosvm_metadata["features"])
113*bb4ee6a4SAndroid Build Coastguard Worker
114*bb4ee6a4SAndroid Build Coastguard Worker    def collect_features(feature_name: str) -> Generator[str, None, None]:
115*bb4ee6a4SAndroid Build Coastguard Worker        yield feature_name
116*bb4ee6a4SAndroid Build Coastguard Worker        for feature in features[feature_name]:
117*bb4ee6a4SAndroid Build Coastguard Worker            if feature in features:
118*bb4ee6a4SAndroid Build Coastguard Worker                yield from collect_features(feature)
119*bb4ee6a4SAndroid Build Coastguard Worker            else:
120*bb4ee6a4SAndroid Build Coastguard Worker                # optional crate is enabled through sub-feature of the crate.
121*bb4ee6a4SAndroid Build Coastguard Worker                # e.g. protos optional crate/feature is enabled by protos/plugin
122*bb4ee6a4SAndroid Build Coastguard Worker                optional_crate_name = feature.split("/")[0]
123*bb4ee6a4SAndroid Build Coastguard Worker                if (
124*bb4ee6a4SAndroid Build Coastguard Worker                    optional_crate_name in features
125*bb4ee6a4SAndroid Build Coastguard Worker                    and features[optional_crate_name][0] == f"dep:{optional_crate_name}"
126*bb4ee6a4SAndroid Build Coastguard Worker                ):
127*bb4ee6a4SAndroid Build Coastguard Worker                    yield optional_crate_name
128*bb4ee6a4SAndroid Build Coastguard Worker
129*bb4ee6a4SAndroid Build Coastguard Worker    all_platform_features = set(
130*bb4ee6a4SAndroid Build Coastguard Worker        (
131*bb4ee6a4SAndroid Build Coastguard Worker            *collect_features("all-x86_64"),
132*bb4ee6a4SAndroid Build Coastguard Worker            *collect_features("all-aarch64"),
133*bb4ee6a4SAndroid Build Coastguard Worker            *collect_features("all-armhf"),
134*bb4ee6a4SAndroid Build Coastguard Worker            *collect_features("all-mingw64"),
135*bb4ee6a4SAndroid Build Coastguard Worker            *collect_features("all-msvc64"),
136*bb4ee6a4SAndroid Build Coastguard Worker            *collect_features("all-riscv64"),
137*bb4ee6a4SAndroid Build Coastguard Worker            *collect_features("all-android"),
138*bb4ee6a4SAndroid Build Coastguard Worker        )
139*bb4ee6a4SAndroid Build Coastguard Worker    )
140*bb4ee6a4SAndroid Build Coastguard Worker    disabled_features = [
141*bb4ee6a4SAndroid Build Coastguard Worker        feature
142*bb4ee6a4SAndroid Build Coastguard Worker        for feature in features
143*bb4ee6a4SAndroid Build Coastguard Worker        if feature not in all_platform_features and feature not in KNOWN_DISABLED_FEATURES
144*bb4ee6a4SAndroid Build Coastguard Worker    ]
145*bb4ee6a4SAndroid Build Coastguard Worker    if disabled_features:
146*bb4ee6a4SAndroid Build Coastguard Worker        raise Exception(
147*bb4ee6a4SAndroid Build Coastguard Worker            f"The features {', '.join(disabled_features)} are not enabled in upstream crosvm builds."
148*bb4ee6a4SAndroid Build Coastguard Worker        )
149*bb4ee6a4SAndroid Build Coastguard Worker
150*bb4ee6a4SAndroid Build Coastguard Worker
151*bb4ee6a4SAndroid Build Coastguard WorkerLICENSE_HEADER_RE = (
152*bb4ee6a4SAndroid Build Coastguard Worker    r".*Copyright (?P<year>20[0-9]{2})(?:-20[0-9]{2})? The ChromiumOS Authors\n"
153*bb4ee6a4SAndroid Build Coastguard Worker    r".*Use of this source code is governed by a BSD-style license that can be\n"
154*bb4ee6a4SAndroid Build Coastguard Worker    r".*found in the LICENSE file\.\n"
155*bb4ee6a4SAndroid Build Coastguard Worker    r"( *\*/\n)?"  # allow the end of a C-style comment before the blank line
156*bb4ee6a4SAndroid Build Coastguard Worker    r"\n"
157*bb4ee6a4SAndroid Build Coastguard Worker)
158*bb4ee6a4SAndroid Build Coastguard Worker
159*bb4ee6a4SAndroid Build Coastguard WorkerNEW_LICENSE_HEADER = [
160*bb4ee6a4SAndroid Build Coastguard Worker    f"Copyright {datetime.now().year} The ChromiumOS Authors",
161*bb4ee6a4SAndroid Build Coastguard Worker    "Use of this source code is governed by a BSD-style license that can be",
162*bb4ee6a4SAndroid Build Coastguard Worker    "found in the LICENSE file.",
163*bb4ee6a4SAndroid Build Coastguard Worker]
164*bb4ee6a4SAndroid Build Coastguard Worker
165*bb4ee6a4SAndroid Build Coastguard Worker
166*bb4ee6a4SAndroid Build Coastguard Workerdef new_licence_header(file_suffix: str):
167*bb4ee6a4SAndroid Build Coastguard Worker    if file_suffix in (".py", "", ".policy", ".sh"):
168*bb4ee6a4SAndroid Build Coastguard Worker        prefix = "#"
169*bb4ee6a4SAndroid Build Coastguard Worker    else:
170*bb4ee6a4SAndroid Build Coastguard Worker        prefix = "//"
171*bb4ee6a4SAndroid Build Coastguard Worker    return "\n".join(f"{prefix} {line}" for line in NEW_LICENSE_HEADER) + "\n\n"
172*bb4ee6a4SAndroid Build Coastguard Worker
173*bb4ee6a4SAndroid Build Coastguard Worker
174*bb4ee6a4SAndroid Build Coastguard Workerdef check_copyright_header(*files: str, fix: bool = False):
175*bb4ee6a4SAndroid Build Coastguard Worker    "Checks copyright header. Can 'fix' them if needed by adding the header."
176*bb4ee6a4SAndroid Build Coastguard Worker    license_re = re.compile(LICENSE_HEADER_RE, re.MULTILINE)
177*bb4ee6a4SAndroid Build Coastguard Worker    for file_path in (Path(f) for f in files):
178*bb4ee6a4SAndroid Build Coastguard Worker        header = file_path.open("r").read(512)
179*bb4ee6a4SAndroid Build Coastguard Worker        license_match = license_re.search(header)
180*bb4ee6a4SAndroid Build Coastguard Worker        if license_match:
181*bb4ee6a4SAndroid Build Coastguard Worker            continue
182*bb4ee6a4SAndroid Build Coastguard Worker        # Generated files do not need a copyright header.
183*bb4ee6a4SAndroid Build Coastguard Worker        if "generated by" in header:
184*bb4ee6a4SAndroid Build Coastguard Worker            continue
185*bb4ee6a4SAndroid Build Coastguard Worker        if fix:
186*bb4ee6a4SAndroid Build Coastguard Worker            print(f"Adding copyright header: {file_path}")
187*bb4ee6a4SAndroid Build Coastguard Worker            contents = file_path.read_text()
188*bb4ee6a4SAndroid Build Coastguard Worker            file_path.write_text(new_licence_header(file_path.suffix) + contents)
189*bb4ee6a4SAndroid Build Coastguard Worker        else:
190*bb4ee6a4SAndroid Build Coastguard Worker            raise Exception(f"Bad copyright header: {file_path}")
191*bb4ee6a4SAndroid Build Coastguard Worker
192*bb4ee6a4SAndroid Build Coastguard Worker
193*bb4ee6a4SAndroid Build Coastguard Workerdef check_file_ends_with_newline(*files: str, fix: bool = False):
194*bb4ee6a4SAndroid Build Coastguard Worker    "Checks if files end with a newline."
195*bb4ee6a4SAndroid Build Coastguard Worker    for file_path in (Path(f) for f in files):
196*bb4ee6a4SAndroid Build Coastguard Worker        with file_path.open("rb") as file:
197*bb4ee6a4SAndroid Build Coastguard Worker            # Skip empty files
198*bb4ee6a4SAndroid Build Coastguard Worker            file.seek(0, os.SEEK_END)
199*bb4ee6a4SAndroid Build Coastguard Worker            if file.tell() == 0:
200*bb4ee6a4SAndroid Build Coastguard Worker                continue
201*bb4ee6a4SAndroid Build Coastguard Worker            # Check last byte of the file
202*bb4ee6a4SAndroid Build Coastguard Worker            file.seek(-1, os.SEEK_END)
203*bb4ee6a4SAndroid Build Coastguard Worker            file_end = file.read(1)
204*bb4ee6a4SAndroid Build Coastguard Worker            if file_end.decode("utf-8") != "\n":
205*bb4ee6a4SAndroid Build Coastguard Worker                if fix:
206*bb4ee6a4SAndroid Build Coastguard Worker                    file_path.write_text(file_path.read_text() + "\n")
207*bb4ee6a4SAndroid Build Coastguard Worker                else:
208*bb4ee6a4SAndroid Build Coastguard Worker                    raise Exception(f"File does not end with a newline {file_path}")
209*bb4ee6a4SAndroid Build Coastguard Worker
210*bb4ee6a4SAndroid Build Coastguard Worker
211*bb4ee6a4SAndroid Build Coastguard Workerif __name__ == "__main__":
212*bb4ee6a4SAndroid Build Coastguard Worker    run_commands(
213*bb4ee6a4SAndroid Build Coastguard Worker        check_file_ends_with_newline,
214*bb4ee6a4SAndroid Build Coastguard Worker        check_copyright_header,
215*bb4ee6a4SAndroid Build Coastguard Worker        check_rust_features,
216*bb4ee6a4SAndroid Build Coastguard Worker        check_rust_lockfiles,
217*bb4ee6a4SAndroid Build Coastguard Worker        check_line_endings,
218*bb4ee6a4SAndroid Build Coastguard Worker        check_platform_independent,
219*bb4ee6a4SAndroid Build Coastguard Worker    )
220