From 41c2259e1c2ac2e02ce2b19a7a07c82625aa994e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5vard=20Berland?= Date: Wed, 21 Dec 2022 14:10:07 +0100 Subject: [PATCH] clang-format: Remove version check This commit also speeds up `clang-format` when installed from PyPI. It detects whether the `clang-format` executable is a Python wrapper and finds where the actual binary is location. Co-authored-by: Zohar Malamant --- .github/workflows/style.yml | 2 +- CMakeLists.txt | 3 +- script/clang-format | 66 ++++++++++++++++++++++++++----------- 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 93df0a8fa..c64ff0b8e 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -9,7 +9,7 @@ jobs: runs-on: "ubuntu-latest" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install dependencies run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 06d07ef7c..91f2fef50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -236,7 +236,8 @@ if(MSVC) add_definitions("/W3 /D_CRT_SECURE_NO_WARNINGS /wd4996") endif() -list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules "${CMAKE_CURRENT_BINARY_DIR}") +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules + "${CMAKE_CURRENT_BINARY_DIR}") find_package(CXX11Features) # ----------------------------------------------------------------- diff --git a/script/clang-format b/script/clang-format index bb8118725..38eba53cb 100755 --- a/script/clang-format +++ b/script/clang-format @@ -1,26 +1,51 @@ #!/usr/bin/env python3 +import shutil import sys -import re -from typing import Generator -from pathlib import Path -from subprocess import check_output, CalledProcessError, STDOUT from argparse import ArgumentParser +from pathlib import Path +from subprocess import STDOUT, CalledProcessError, check_output +from typing import Iterator, Optional - -CLANG_VERSIONS = ("10", "11", "12") DIRECTORIES = ["applications", "lib"] -def check_version(clang_format: str) -> None: - version_str = check_output([clang_format, "--version"]).decode() - version_res = re.search(r"version (\d+).(\d+).(\d+)", version_str) - assert version_res - - major = version_res[1] - if major not in CLANG_VERSIONS: - sys.exit( - f"Version of clang-format ({clang_format}) is {major}, expected it to be one of {', '.join(CLANG_VERSIONS)}" +def find_clang_format_binary(clang_format: Optional[str]) -> Path: + """ + Looks for clang-format binary by searching the PATH environment + variable. Detect if clang-format was installed from PyPI and use the actual + binary rather than the incredibly slow Python wrapper. + """ + if clang_format is None: + clang_format = shutil.which("clang-format") + if clang_format is None: + sys.exit("No viable executable 'clang-format' found in PATH") + + with open(clang_format, "rb") as f: + head = f.read(512) + + if head[:2] != b"#!": + # File does not contain shebang, assuming real clang-format + print(f"Using clang-format: {clang_format}") + return Path(clang_format) + + # Extract everything between '#!' and newline + python_path = head[2:].split(b"\n")[0].decode().strip() + + # Locate the Python 'clang-format' module path + mod_path = ( + check_output( + [python_path, "-c", "import clang_format;print(clang_format.__file__)"] ) + .decode() + .strip() + ) + + # We assume that the location of the actual binary is always in the same + # location + clang_format_path = Path(mod_path).parent / "data" / "bin" / "clang-format" + + print(f"Using clang-format: {clang_format_path}") + return clang_format_path def source_root() -> Path: @@ -32,7 +57,7 @@ def source_root() -> Path: return node -def enumerate_sources() -> Generator[Path, None, None]: +def enumerate_sources() -> Iterator[Path]: root = source_root() for directory in DIRECTORIES: for extension in "c", "h", "cpp", "hpp": @@ -41,7 +66,7 @@ def enumerate_sources() -> Generator[Path, None, None]: yield path -def reformat(clang_format: str, dry_run: bool, verbose: bool) -> None: +def reformat(clang_format: Path, dry_run: bool, verbose: bool) -> None: total = 0 need_reformat = 0 failed_reformat = 0 @@ -99,7 +124,7 @@ def main() -> None: ) ap.add_argument( "--clang-format", - default="clang-format", + type=str, help="Name/path of the clang-format binary", ) ap.add_argument( @@ -111,8 +136,9 @@ def main() -> None: ) args = ap.parse_args() - check_version(args.clang_format) - reformat(args.clang_format, args.check, args.verbose) + + clang_format = find_clang_format_binary(args.clang_format) + reformat(clang_format, args.check, args.verbose) if __name__ == "__main__":