1+ from concurrent .futures import ThreadPoolExecutor
12import subprocess
23import sys
3- from argparse import ArgumentParser
4+ from argparse import ArgumentParser , ArgumentTypeError
45from pathlib import Path
5- from typing import Optional , Tuple
6+ from typing import List , Optional , Tuple
67
78from cpp_linter_hooks .util import resolve_install , DEFAULT_CLANG_TIDY_VERSION
89
910COMPILE_DB_SEARCH_DIRS = ["build" , "out" , "cmake-build-debug" , "_build" ]
11+ SOURCE_FILE_SUFFIXES = {
12+ ".c" ,
13+ ".cc" ,
14+ ".cp" ,
15+ ".cpp" ,
16+ ".cxx" ,
17+ ".c++" ,
18+ ".cu" ,
19+ ".cuh" ,
20+ ".h" ,
21+ ".hh" ,
22+ ".hpp" ,
23+ ".hxx" ,
24+ ".h++" ,
25+ ".ipp" ,
26+ ".inl" ,
27+ ".ixx" ,
28+ ".tpp" ,
29+ ".txx" ,
30+ }
31+
32+
33+ def _positive_int (value : str ) -> int :
34+ jobs = int (value )
35+ if jobs < 1 :
36+ raise ArgumentTypeError ("--jobs must be greater than 0" )
37+ return jobs
1038
1139parser = ArgumentParser ()
1240parser .add_argument ("--version" , default = DEFAULT_CLANG_TIDY_VERSION )
1341parser .add_argument ("--compile-commands" , default = None , dest = "compile_commands" )
1442parser .add_argument (
1543 "--no-compile-commands" , action = "store_true" , dest = "no_compile_commands"
1644)
45+ parser .add_argument ("-j" , "--jobs" , type = _positive_int , default = 1 )
1746parser .add_argument ("-v" , "--verbose" , action = "store_true" )
1847
1948
@@ -74,6 +103,38 @@ def _exec_clang_tidy(command) -> Tuple[int, str]:
74103 return 1 , str (e )
75104
76105
106+ def _looks_like_source_file (path : str ) -> bool :
107+ return any (suffix .lower () in SOURCE_FILE_SUFFIXES for suffix in Path (path ).suffixes )
108+
109+
110+ def _split_source_files (args : List [str ]) -> Tuple [List [str ], List [str ]]:
111+ split_idx = len (args )
112+ source_files : List [str ] = []
113+ for idx in range (len (args ) - 1 , - 1 , - 1 ):
114+ if not _looks_like_source_file (args [idx ]):
115+ break
116+ source_files .append (args [idx ])
117+ split_idx = idx
118+ return args [:split_idx ], list (reversed (source_files ))
119+
120+
121+ def _combine_outputs (results : List [Tuple [int , str ]]) -> str :
122+ return "\n " .join (output .rstrip ("\n " ) for _ , output in results if output )
123+
124+
125+ def _exec_parallel_clang_tidy (
126+ command_prefix : List [str ], source_files : List [str ], jobs : int
127+ ) -> Tuple [int , str ]:
128+ def run_file (source_file : str ) -> Tuple [int , str ]:
129+ return _exec_clang_tidy (command_prefix + [source_file ])
130+
131+ with ThreadPoolExecutor (max_workers = min (jobs , len (source_files ))) as executor :
132+ results = list (executor .map (run_file , source_files ))
133+
134+ retval = 1 if any (retval != 0 for retval , _ in results ) else 0
135+ return retval , _combine_outputs (results )
136+
137+
77138def run_clang_tidy (args = None ) -> Tuple [int , str ]:
78139 hook_args , other_args = parser .parse_known_args (args )
79140 if hook_args .version :
@@ -90,6 +151,12 @@ def run_clang_tidy(args=None) -> Tuple[int, str]:
90151 )
91152 other_args = ["-p" , compile_db_path ] + other_args
92153
154+ clang_tidy_args , source_files = _split_source_files (other_args )
155+ if hook_args .jobs > 1 and len (source_files ) > 1 :
156+ return _exec_parallel_clang_tidy (
157+ ["clang-tidy" ] + clang_tidy_args , source_files , hook_args .jobs
158+ )
159+
93160 return _exec_clang_tidy (["clang-tidy" ] + other_args )
94161
95162
0 commit comments