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>
205 lines
6.4 KiB
Python
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()
|