#!/usr/bin/env python # License: GPLv3 Copyright: 2026, Kovid Goyal import concurrent.futures import hashlib import json import os import subprocess import sys import tempfile import threading base = os.path.dirname(os.path.abspath(__file__)) ruff = subprocess.Popen(['ruff', 'format'], cwd=base, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) go = subprocess.Popen('gofmt -s -l -w tools kittens'.split(), cwd=base, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) ruff_output = b'' go_output = b'' def wait_ruff() -> None: global ruff_output ruff_output = ruff.communicate()[0] def wait_go() -> None: global go_output go_output = go.communicate()[0] threading.Thread(target=wait_ruff).start() threading.Thread(target=wait_go).start() clang_files = [] for x in os.listdir(base): if x in ('dist', 'build', 'bypy', '3rdparty') or x.startswith('.'): continue for root, dirnames, files in os.walk(os.path.join(base, x)): for file in files: if file.startswith('wayland-') and os.path.basename(root) == 'glfw': continue ext = os.path.splitext(file)[1] if ext in ('.c', '.h', '.m', '.slang'): clang_files.append(os.path.join(root, file)) CACHE_DIR = os.path.join(base, '.cache', 'autoformat') CACHE_FILE = os.path.join(CACHE_DIR, 'clang_format.json') def data_hash(src: bytes) -> str: return hashlib.md5(src).hexdigest() def file_hash(path: str) -> tuple[str, bytes]: with open(path, 'rb') as f: src = f.read() return data_hash(src), src def load_cache() -> dict[str, str]: try: with open(CACHE_FILE) as f: return json.load(f) except (FileNotFoundError, json.JSONDecodeError): return {} def save_cache(cache: dict[str, str]) -> None: os.makedirs(CACHE_DIR, exist_ok=True) fd, tmp = tempfile.mkstemp(dir=CACHE_DIR) try: with os.fdopen(fd, 'w') as f: json.dump(cache, f, indent=2) os.replace(tmp, CACHE_FILE) except Exception: os.unlink(tmp) raise cache: dict[str, str] = load_cache() cache_lock = threading.Lock() def run_clang_format(file_path: str) -> tuple[bool, str, str]: rel_path = os.path.relpath(file_path, base) current_hash, src = file_hash(file_path) with cache_lock: if cache.get(rel_path) == current_hash: return True, '', '' fn = file_path sf = '.clang-format' if file_path.endswith('.slang'): fn += '.cs' sf = '.clang-format-for-slang' cmd = ['clang-format', '--style=file:' + sf, '--assume-filename=' + fn] result = subprocess.run(cmd, capture_output=True, input=src) if result.returncode != 0: return False, file_path, result.stderr.decode() if result.stdout: new_hash = data_hash(result.stdout) with open(file_path, 'wb') as f: f.write(result.stdout) else: new_hash = current_hash with cache_lock: cache[rel_path] = new_hash return True, '', '' clang_failed = False with concurrent.futures.ThreadPoolExecutor(max_workers=os.cpu_count()) as executor: futures = {executor.submit(run_clang_format, f): f for f in clang_files} for future in concurrent.futures.wait(futures)[0]: success, file_path, error_msg = future.result() if not success: print(f'[FAILED] {file_path}\n{error_msg}', file=sys.stderr) clang_failed = True save_cache(cache) ruff.wait() go.wait() if ruff.wait() != 0: sys.stderr.buffer.write(ruff_output) raise SystemExit('Formatting of Python code failed') if go.wait() != 0: sys.stderr.buffer.write(go_output) raise SystemExit('Formatting of Go code failed') raise SystemExit('Formatting of C files failed' if clang_failed else 0)