our_knp/examples/tools/cltcache.py
int 8f43927575 Initial import: KNP experiments (examples, viz, cats-dogs, reports)
examples/ is based on KasperskyLab/knp at 55470ed6 with local changes
to the mnist-learn altai/blifat models, cuda-backend CMake and model_loader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:17:15 +03:00

205 lines
6.4 KiB
Python

#!/usr/bin/env python3
"""
A simple clang-tidy caching application.
Prefix calls to clang-tidy to cache
their results for faster static code analysis.
Source: https://github.com/freedick/cltcache
"""
import configparser
import gzip
import hashlib
import os
import pathlib
import re
import subprocess
import sys
import time
def save_to_file_raw(data, filename):
with open(filename, "wb") as f:
f.write(data)
def save_to_file(string, filename):
save_to_file_raw(string.encode("utf-8"), filename)
def compress_to_file(data, filename):
save_to_file_raw(gzip.compress(data), filename)
def read_from_file_raw(filename):
with open(filename, "rb") as f:
return f.read()
def read_from_file(filename):
return read_from_file_raw(filename).decode("utf-8")
def decompress_from_file(filename):
return gzip.decompress(read_from_file_raw(filename))
def sha256(string):
m = hashlib.sha256()
m.update(string.encode('utf-8'))
return m.hexdigest()
def file_age(filepath):
return time.time() - os.path.getmtime(filepath)
def run_command(command):
return subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
def run_get_stdout(command, ignore_errors=False):
"""
Run a command and forward its stdout and stderr if exit code is nonzero,
otherwise return stdout.
"""
result = run_command(command)
if not ignore_errors and result.returncode != 0:
raise Exception(f"Bad exit code when running: {command}")
return result.stdout.decode("utf-8")
def remove_o_flag(compile_args):
if "-o" in compile_args:
oflag_index = compile_args.index('-o')
return compile_args[:oflag_index] + compile_args[oflag_index + 2 :]
return compile_args
def get_preproc_hash(compile_args, config):
compile_args = remove_o_flag(compile_args)
preproc_flag = "-E"
keep_comments_flag = config.get("preprocessor", "preserve_comments", fallback="-C")
preproc_command = config.get("preprocessor", "command", fallback="c++")
preproc_source = run_get_stdout(
[preproc_command] + compile_args + [preproc_flag, keep_comments_flag],
config.getboolean("preprocessor", "ignore_errors", fallback=False),
)
verbose = config.getboolean("behavior", "verbose", fallback=False)
if verbose:
print("cltcache length of preproccesed source:", len(preproc_source))
preproc_hash = sha256(preproc_source)
return preproc_hash
def compute_cache_key(clang_tidy_call, config):
clang_tidy = clang_tidy_call[0]
if "--" not in clang_tidy_call:
raise Exception("Missing '--' flag in compiler options")
forwardflag_index = clang_tidy_call.index("--")
compile_args = clang_tidy_call[forwardflag_index + 1 :]
clang_tidy_args = clang_tidy_call[1:forwardflag_index]
preproc_hash = get_preproc_hash(compile_args, config)
version_out = run_get_stdout([clang_tidy] + ["--version"])
version = ",".join(re.findall(r'[0-9]+\.[0-9]+\.?[0-9]*', version_out))
version_hash = sha256(version)
enabled_checks = run_get_stdout([clang_tidy] + clang_tidy_args + ["--list-checks"])
enabled_checks_hash = sha256(enabled_checks)
return sha256(preproc_hash + enabled_checks_hash + version_hash)[:-16]
def init_cltcache():
cltcache_path = os.environ.get("CLTCACHE_DIR", pathlib.Path().home() / ".cltcache")
cltcache_path.mkdir(parents=True, exist_ok=True)
config = configparser.ConfigParser()
config.read(cltcache_path / "cltcache.cfg")
return cltcache_path, config
def cache_clang_tidy(clang_tidy_call):
print(f"000Running CLang Tidy: ${clang_tidy_call}")
cltcache_path, config = init_cltcache()
cache_path, cat_path, out_path, err_path = (None, None, None, None)
verbose = config.getboolean("behavior", "verbose", fallback=False)
if verbose:
print("cltcache computing cache key")
try:
cache_key = compute_cache_key(clang_tidy_call, config)
if verbose:
print("cltcache key:", cache_key)
cat_path = cltcache_path / cache_key[0]
cache_path = cat_path / cache_key
out_path = cache_path.with_suffix(".out.gz")
err_path = cache_path.with_suffix(".err.gz")
if os.path.exists(cache_path):
if verbose:
print("cltcache hit!")
if os.path.exists(out_path):
clang_tidy_stdout = decompress_from_file(out_path)
if clang_tidy_stdout:
print(
clang_tidy_stdout.decode("utf-8"),
)
if os.path.exists(err_path):
clang_tidy_stderr = decompress_from_file(err_path)
if clang_tidy_stderr:
print(
clang_tidy_stderr.decode("utf-8"),
file=sys.stderr,
)
sys.exit(int(read_from_file(cache_path)))
elif verbose:
print("cltcache miss...")
except Exception as e:
if verbose:
print("cltcache", e)
print("cltcache Preprocessing failed! Forwarding call without caching...", file=sys.stderr)
print(f"Running CLang Tidy: ${clang_tidy_call}")
result = run_command(clang_tidy_call)
clt_success = result.returncode == 0
preproc_success = cache_path is not None
cache_results = (clt_success or config.getboolean("behavior", "cache_failure", fallback=True)) and preproc_success
if cache_results:
cat_path.mkdir(parents=True, exist_ok=True)
if result.stdout:
print(
result.stdout.decode("utf-8"),
)
if cache_results:
compress_to_file(result.stdout, out_path)
if result.stderr:
print(
result.stderr.decode("utf-8"),
file=sys.stderr,
)
if cache_results:
compress_to_file(result.stderr, err_path)
if cache_results:
if verbose:
print("cltcache caching results...")
save_to_file(str(result.returncode), cache_path)
sys.exit(result.returncode)
def main():
if len(sys.argv) <= 1:
command = sys.argv[0]
helptext = (
"Usage:\n"
f" {command}\n"
f" {command} clang-tidy [clang-tidy options] -- "
"-o output [compiler options]"
)
print(helptext)
else:
cache_clang_tidy(sys.argv[1:])
if __name__ == "__main__":
main()