|
| 1 | +# SPDX-License-Identifier: Apache-2.0 |
| 2 | +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | +""" |
| 4 | +Run mypy on changed files. |
| 5 | +
|
| 6 | +This script is designed to be used as a pre-commit hook. It runs mypy |
| 7 | +on files that have been changed. It groups files into different mypy calls |
| 8 | +based on their directory to avoid import following issues. |
| 9 | +
|
| 10 | +Usage: |
| 11 | + python tools/pre_commit/mypy.py <ci> <python_version> <changed_files...> |
| 12 | +
|
| 13 | +Args: |
| 14 | + ci: "1" if running in CI, "0" otherwise. In CI, follow_imports is set to |
| 15 | + "silent" for the main group of files. |
| 16 | + python_version: Python version to use (e.g., "3.10") or "local" to use |
| 17 | + the local Python version. |
| 18 | + changed_files: List of changed files to check. |
| 19 | +""" |
| 20 | + |
| 21 | +import subprocess |
| 22 | +import sys |
| 23 | +from typing import Optional |
| 24 | + |
| 25 | +import regex as re |
| 26 | + |
| 27 | +FILES = [ |
| 28 | + "vllm/*.py", |
| 29 | + "vllm/assets", |
| 30 | + "vllm/entrypoints", |
| 31 | + "vllm/inputs", |
| 32 | + "vllm/logging_utils", |
| 33 | + "vllm/multimodal", |
| 34 | + "vllm/platforms", |
| 35 | + "vllm/transformers_utils", |
| 36 | + "vllm/triton_utils", |
| 37 | + "vllm/usage", |
| 38 | +] |
| 39 | + |
| 40 | +# After fixing errors resulting from changing follow_imports |
| 41 | +# from "skip" to "silent", move the following directories to FILES |
| 42 | +SEPARATE_GROUPS = [ |
| 43 | + "tests", |
| 44 | + "vllm/attention", |
| 45 | + "vllm/compilation", |
| 46 | + "vllm/distributed", |
| 47 | + "vllm/engine", |
| 48 | + "vllm/executor", |
| 49 | + "vllm/inputs", |
| 50 | + "vllm/lora", |
| 51 | + "vllm/model_executor", |
| 52 | + "vllm/plugins", |
| 53 | + "vllm/worker", |
| 54 | + "vllm/v1", |
| 55 | +] |
| 56 | + |
| 57 | +# TODO(woosuk): Include the code from Megatron and HuggingFace. |
| 58 | +EXCLUDE = [ |
| 59 | + "vllm/model_executor/parallel_utils", |
| 60 | + "vllm/model_executor/models", |
| 61 | + "vllm/model_executor/layers/fla/ops", |
| 62 | + # Ignore triton kernels in ops. |
| 63 | + "vllm/attention/ops", |
| 64 | +] |
| 65 | + |
| 66 | + |
| 67 | +def group_files(changed_files: list[str]) -> dict[str, list[str]]: |
| 68 | + """ |
| 69 | + Group changed files into different mypy calls. |
| 70 | +
|
| 71 | + Args: |
| 72 | + changed_files: List of changed files. |
| 73 | +
|
| 74 | + Returns: |
| 75 | + A dictionary mapping file group names to lists of changed files. |
| 76 | + """ |
| 77 | + exclude_pattern = re.compile(f"^{'|'.join(EXCLUDE)}.*") |
| 78 | + files_pattern = re.compile(f"^({'|'.join(FILES)}).*") |
| 79 | + file_groups = {"": []} |
| 80 | + file_groups.update({k: [] for k in SEPARATE_GROUPS}) |
| 81 | + for changed_file in changed_files: |
| 82 | + # Skip files which should be ignored completely |
| 83 | + if exclude_pattern.match(changed_file): |
| 84 | + continue |
| 85 | + # Group files by mypy call |
| 86 | + if files_pattern.match(changed_file): |
| 87 | + file_groups[""].append(changed_file) |
| 88 | + continue |
| 89 | + else: |
| 90 | + for directory in SEPARATE_GROUPS: |
| 91 | + if re.match(f"^{directory}.*", changed_file): |
| 92 | + file_groups[directory].append(changed_file) |
| 93 | + break |
| 94 | + return file_groups |
| 95 | + |
| 96 | + |
| 97 | +def mypy(targets: list[str], python_version: Optional[str], |
| 98 | + follow_imports: Optional[str], file_group: str) -> int: |
| 99 | + """ |
| 100 | + Run mypy on the given targets. |
| 101 | + |
| 102 | + Args: |
| 103 | + targets: List of files or directories to check. |
| 104 | + python_version: Python version to use (e.g., "3.10") or None to use |
| 105 | + the default mypy version. |
| 106 | + follow_imports: Value for the --follow-imports option or None to use |
| 107 | + the default mypy behavior. |
| 108 | + file_group: The file group name for logging purposes. |
| 109 | +
|
| 110 | + Returns: |
| 111 | + The return code from mypy. |
| 112 | + """ |
| 113 | + args = ["mypy"] |
| 114 | + if python_version is not None: |
| 115 | + args += ["--python-version", python_version] |
| 116 | + if follow_imports is not None: |
| 117 | + args += ["--follow-imports", follow_imports] |
| 118 | + print(f"$ {' '.join(args)} {file_group}") |
| 119 | + return subprocess.run(args + targets, check=False).returncode |
| 120 | + |
| 121 | + |
| 122 | +def main(): |
| 123 | + ci = sys.argv[1] == "1" |
| 124 | + python_version = sys.argv[2] |
| 125 | + file_groups = group_files(sys.argv[3:]) |
| 126 | + |
| 127 | + if python_version == "local": |
| 128 | + python_version = f"{sys.version_info.major}.{sys.version_info.minor}" |
| 129 | + |
| 130 | + returncode = 0 |
| 131 | + for file_group, changed_files in file_groups.items(): |
| 132 | + follow_imports = None if ci and file_group == "" else "skip" |
| 133 | + if changed_files: |
| 134 | + returncode |= mypy(changed_files, python_version, follow_imports, |
| 135 | + file_group) |
| 136 | + return returncode |
| 137 | + |
| 138 | + |
| 139 | +if __name__ == "__main__": |
| 140 | + sys.exit(main()) |
0 commit comments