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