Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:10:43

0001 #!/usr/bin/env python3
0002 import argparse
0003 from concurrent.futures import ThreadPoolExecutor, as_completed
0004 from multiprocessing import cpu_count
0005 import subprocess
0006 from subprocess import check_call, check_output, CalledProcessError
0007 from pathlib import Path
0008 import re
0009 import sys
0010 import os
0011 import threading
0012 
0013 import rich.console
0014 import rich.progress
0015 import rich.panel
0016 import rich.live
0017 import rich.text
0018 import rich.table
0019 import rich.rule
0020 import rich.spinner
0021 
0022 
0023 def which(cmd: str):
0024     try:
0025         return check_output(["command", "-v", cmd]).decode().strip()
0026     except CalledProcessError:
0027         return None
0028 
0029 
0030 def main():
0031     p = argparse.ArgumentParser()
0032     p.add_argument("--clang-tidy", default=which("clang-tidy"))
0033     p.add_argument("--clang-format", default=which("clang-format"))
0034     p.add_argument("--jobs", "-j", type=int, default=cpu_count())
0035     p.add_argument("--fix", action="store_true")
0036     p.add_argument("--include", action="append", default=[])
0037     p.add_argument("--exclude", action="append", default=[])
0038     p.add_argument("--ignore-compiler-errors", action="store_true")
0039     p.add_argument("build", type=Path)
0040     p.add_argument("source", type=Path)
0041 
0042     args = p.parse_args()
0043 
0044     assert args.clang_tidy is not None, "clang-tidy not found"
0045     assert args.clang_format is not None, "clang-format not found"
0046 
0047     args.include = [re.compile(f) for f in args.include]
0048     args.exclude = [re.compile(f) for f in args.exclude]
0049 
0050     check_call([args.clang_tidy, "--version"], stdout=subprocess.DEVNULL)
0051     check_call([args.clang_format, "--version"], stdout=subprocess.DEVNULL)
0052 
0053     assert (
0054         args.build.exists() and args.build.is_dir()
0055     ), f"{args.build} is not a directory"
0056     assert (
0057         args.source.exists() and args.source.is_dir()
0058     ), f"{args.source} is not a directory"
0059 
0060     futures = []
0061     files = []
0062     active_files = {}
0063     active_file_lock = threading.Lock()
0064 
0065     def run(file: Path):
0066         with active_file_lock:
0067             active_files[threading.current_thread().ident] = file
0068         cmd = [args.clang_tidy, "-p", args.build, file]
0069         if args.fix:
0070             cmd.append("-fix")
0071 
0072         try:
0073             out = check_output(cmd, stderr=subprocess.STDOUT).decode().strip()
0074             error = False
0075         except CalledProcessError as e:
0076             out = e.output.decode().strip()
0077             if args.ignore_compiler_errors and "Found compiler error(s)." in out:
0078                 out = "Found compiler error(s)."
0079                 error = False
0080             else:
0081                 error = True
0082         finally:
0083             with active_file_lock:
0084                 active_files[threading.current_thread().ident] = None
0085         return file, out, error
0086 
0087     for dirpath, _, filenames in os.walk(args.source):
0088         dirpath = Path(dirpath)
0089         for file in filenames:
0090             file = dirpath / file
0091             if (
0092                 file.suffix in (".hpp", ".cpp", ".ipp")
0093                 and (
0094                     len(args.include) == 0
0095                     or any(flt.match(str(file)) for flt in args.include)
0096                 )
0097                 and not any(flt.match(str(file)) for flt in args.exclude)
0098             ):
0099                 files.append(file)
0100 
0101     with ThreadPoolExecutor(args.jobs) as tp:
0102         for file in files:
0103             assert file.exists(), f"{file} does not exist"
0104             futures.append(tp.submit(run, file))
0105 
0106         error = False
0107 
0108         console = rich.console.Console()
0109 
0110         prog = rich.progress.Progress()
0111         log = []
0112 
0113         def make_display():
0114             t = rich.table.Table.grid()
0115             t.add_column()
0116             t.add_column()
0117             with active_file_lock:
0118                 for f in active_files.values():
0119                     if f is None:
0120                         t.add_row("")
0121                     else:
0122                         t.add_row(rich.spinner.Spinner("dots", style="green"), f" {f}")
0123 
0124             ot = rich.table.Table.grid(expand=True)
0125             ot.add_column(ratio=1)
0126             ot.add_column(ratio=1)
0127 
0128             def emoji(err):
0129                 return ":red_circle:" if err else ":green_circle:"
0130 
0131             ot.add_row(
0132                 t,
0133                 rich.console.Group(
0134                     *[f"{emoji(err)} {line}" for err, line in log[-args.jobs :]]
0135                 ),
0136             )
0137 
0138             return rich.console.Group(rich.rule.Rule(), ot, prog)
0139 
0140         task = prog.add_task("Running clang-tidy", total=len(futures))
0141 
0142         with rich.live.Live(
0143             make_display(), console=console, refresh_per_second=20, transient=False
0144         ) as live:
0145             for f in as_completed(futures):
0146                 file, result, this_error = f.result()
0147                 log.append((this_error, file))
0148                 error = this_error or error
0149                 console.print(
0150                     rich.panel.Panel(
0151                         result, title=str(file), style="red" if this_error else ""
0152                     )
0153                 )
0154                 prog.advance(task)
0155                 live.update(make_display())
0156             live.refresh()
0157 
0158         if error:
0159             return 1
0160         else:
0161             return 0
0162 
0163 
0164 if __name__ == "__main__":
0165     sys.exit(main())