From f13e194490b9cd73a10f577620c31944bb773f53 Mon Sep 17 00:00:00 2001 From: Lars Glud Date: Mon, 15 Sep 2025 12:27:47 +0200 Subject: [PATCH] Change to use a file which list all the files required to be checked/formatted. Reason is that at least on windows it failed with too long command line. --- .dev/format.py | 124 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 116 insertions(+), 8 deletions(-) diff --git a/.dev/format.py b/.dev/format.py index 720abc7a273..5e1a75e8005 100644 --- a/.dev/format.py +++ b/.dev/format.py @@ -4,6 +4,10 @@ import sys import subprocess import argparse +import tempfile +import shutil +import re +from typing import Iterable, List EXTENSIONS = (".c", ".h", ".cpp", ".hpp", ".cxx", ".hxx", ".cu") WHITELIST_FILE = os.path.join(".dev", "whitelist.txt") @@ -33,6 +37,72 @@ def find_files(path): if file.endswith(EXTENSIONS): yield os.path.join(root, file) +def run_clang(cmd_args: List[str]) -> subprocess.CompletedProcess: + try: + return subprocess.run( + cmd_args, + capture_output=True, + text=True, + encoding="utf-8" + ) + except FileNotFoundError as e: + # Provide actionable feedback if the executable isn't found. + print("Error: failed to launch clang-format.") + print( + "'pipx' may not be available on PATH.\n" + "Install one of the following and try again:\n" + " - pipx (https://pypa.github.io/pipx/)\n" + ) + raise + +def write_response_file(files: Iterable[str]) -> str: + """Write file list to a clang/LLVM-style response file and return its path. + + Each path is written on its own line; paths with spaces are quoted. + """ + fd, path = tempfile.mkstemp(prefix="clang_format_", suffix=".rsp") + try: + with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as tmp: + for p in files: + if any(ch.isspace() for ch in p): + tmp.write(f'"{p}"\n') + else: + tmp.write(p + "\n") + except Exception: + # Ensure file descriptor gets closed on error + try: + os.close(fd) + except Exception: + pass + raise + return path + +def parse_violations(text: str) -> List[str]: + files: List[str] = [] + if not text: + return files + # Patterns seen across clang-format versions + patterns = [ + re.compile(r"^(?P.+?):\d+:\d+:\s+(?:error|warning):.*?(?:code should be clang-formatted|would be reformatted|not formatted)", re.IGNORECASE), + re.compile(r"^(?:error|warning):\s.*?would be reformatted:\s(?P.+)$", re.IGNORECASE), + ] + for line in text.splitlines(): + line = line.strip() + for pat in patterns: + m = pat.match(line) + if m: + f = m.group("file").strip() + files.append(f) + break + # Deduplicate preserving order + seen = set() + uniq: List[str] = [] + for f in files: + if f not in seen: + seen.add(f) + uniq.append(f) + return uniq + def main(): parser = argparse.ArgumentParser( description="Format C/C++/CUDA files using clang-format via pipx." @@ -60,18 +130,56 @@ def main(): all_files = list(set(all_files)) # Remove duplicates + # Print number of files required formatting + print(f"Found {len(all_files)} files to format/check.") + if not all_files: print("No files found to format.") sys.exit(0) - check_cmd = ["pipx", "run", "clang-format==14.0.3", "--dry-run", "--Werror", "--style=file"] + all_files - result = subprocess.run(check_cmd, capture_output=True, text=True) - if result.returncode != 0: - format_cmd = ["pipx", "run", "clang-format==14.0.3", "-i", "--style=file"] + all_files - subprocess.run(format_cmd) - if not manual_mode: - sys.exit(1) # Only fail for pre-commit - # In manual mode, just continue and exit 0 + rsp_path = None + rsp_path_fix = None + try: + # Prefer response file to avoid command length issues on Windows. + rsp_path = write_response_file(all_files) + pipx_path = shutil.which("pipx") + check_cmd = [pipx_path, "run", "clang-format==14.0.3", "--dry-run", "--Werror", "--style=file", f"@{rsp_path}"] + result = run_clang(check_cmd) + + offenders = parse_violations(result.stderr) or parse_violations(result.stdout) + + if offenders: + print(f"Formatting required for {len(offenders)} files.") + # Format only the offending files for speed + rsp_path_fix = write_response_file(offenders) + pipx_path = shutil.which("pipx") + format_cmd = [pipx_path, "run", "clang-format==14.0.3", "-i", "--style=file", f"@{rsp_path_fix}"] + fmt_result = run_clang(format_cmd) + # If pre-commit mode, fail to indicate changes were needed + if not manual_mode: + sys.stdout.write(result.stdout) + sys.stderr.write(result.stderr) + sys.stdout.write(fmt_result.stdout) + sys.stderr.write(fmt_result.stderr) + sys.exit(1) + # In manual mode, continue and exit 0 + else: + # No formatting violations parsed. If clang-format errored for other reasons, propagate. + if result.returncode != 0: + sys.stdout.write(result.stdout) + sys.stderr.write(result.stderr) + sys.exit(result.returncode) + finally: + if rsp_path and os.path.exists(rsp_path): + try: + os.remove(rsp_path) + except OSError: + pass + if rsp_path_fix and os.path.exists(rsp_path_fix): + try: + os.remove(rsp_path_fix) + except OSError: + pass sys.exit(0)