55from pathlib import Path
66from typing import List , Optional , Tuple
77
8- from cpp_linter_hooks .util import resolve_install , DEFAULT_CLANG_TIDY_VERSION
8+ from cpp_linter_hooks .util import resolve_install_with_diagnostics
99
1010COMPILE_DB_SEARCH_DIRS = ["build" , "out" , "cmake-build-debug" , "_build" ]
1111SOURCE_FILE_SUFFIXES = {
2828 ".tpp" ,
2929 ".txx" ,
3030}
31+ COMPILE_COMMANDS_HINT = """\
32+ Generate compile_commands.json with one of:
33+ CMake: cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
34+ Meson: meson setup builddir
35+ Then run clang-tidy with --compile-commands=build or --compile-commands=builddir."""
36+
37+ MSVC_HINT = """\
38+ Windows/MSVC clang-tidy hints:
39+ - Run from a Visual Studio Developer Command Prompt, or call vcvars64.bat first.
40+ - Make sure the Windows SDK and MSVC include paths are visible in that shell.
41+ - For MSVC-style compile databases, try --extra-arg-before=--driver-mode=cl.
42+ - If using CMake, generate compile_commands.json from the same toolchain."""
3143
3244
3345def _positive_int (value : str ) -> int :
@@ -38,7 +50,7 @@ def _positive_int(value: str) -> int:
3850
3951
4052parser = ArgumentParser ()
41- parser .add_argument ("--version" , default = DEFAULT_CLANG_TIDY_VERSION )
53+ parser .add_argument ("--version" , default = None )
4254parser .add_argument ("--compile-commands" , default = None , dest = "compile_commands" )
4355parser .add_argument (
4456 "--no-compile-commands" , action = "store_true" , dest = "no_compile_commands"
@@ -55,6 +67,17 @@ def _find_compile_commands() -> Optional[str]:
5567 return None
5668
5769
70+ def _compile_commands_not_found_message (path : Optional [str ] = None ) -> str :
71+ if path is None :
72+ return "No compile_commands.json was found in common build directories.\n \n " + (
73+ COMPILE_COMMANDS_HINT
74+ )
75+ return (
76+ f"--compile-commands: no compile_commands.json in '{ path } '.\n \n "
77+ f"{ COMPILE_COMMANDS_HINT } "
78+ )
79+
80+
5881def _resolve_compile_db (
5982 hook_args , other_args
6083) -> Tuple [Optional [str ], Optional [Tuple [int , str ]]]:
@@ -79,8 +102,7 @@ def _resolve_compile_db(
79102 if not p .is_dir () or not (p / "compile_commands.json" ).exists ():
80103 return None , (
81104 1 ,
82- f"--compile-commands: no compile_commands.json"
83- f" in '{ hook_args .compile_commands } '" ,
105+ _compile_commands_not_found_message (hook_args .compile_commands ),
84106 )
85107 return hook_args .compile_commands , None
86108
@@ -90,13 +112,68 @@ def _resolve_compile_db(
90112 return None , None
91113
92114
115+ def _looks_like_compile_db_error (output : str ) -> bool :
116+ lower_output = output .lower ()
117+ compile_db_error = "compile_commands.json" in lower_output and any (
118+ pattern in lower_output
119+ for pattern in (
120+ "not found" ,
121+ "no such file" ,
122+ "missing" ,
123+ "error" ,
124+ "could not" ,
125+ )
126+ )
127+ return compile_db_error or any (
128+ pattern in lower_output
129+ for pattern in (
130+ "error while trying to load a compilation database" ,
131+ "could not auto-detect compilation database" ,
132+ "no compilation database found" ,
133+ )
134+ )
135+
136+
137+ def _looks_like_msvc_error (output : str ) -> bool :
138+ lower_output = output .lower ()
139+ cl_driver_error = "cl.exe" in lower_output and any (
140+ pattern in lower_output
141+ for pattern in ("not found" , "doesn't exist" , "unable to execute" )
142+ )
143+ msvc_patterns = (
144+ "unable to find a visual studio installation" ,
145+ "visual studio installation" ,
146+ "vcruntime.h" ,
147+ "windows.h' file not found" ,
148+ "sal.h' file not found" ,
149+ "msvc" ,
150+ "unknown argument: '/" ,
151+ "unsupported option '/" ,
152+ "argument unused during compilation: '/" ,
153+ )
154+ return cl_driver_error or any (pattern in lower_output for pattern in msvc_patterns )
155+
156+
157+ def _append_guidance (output : str ) -> str :
158+ hints : List [str ] = []
159+ if _looks_like_compile_db_error (output ) and COMPILE_COMMANDS_HINT not in output :
160+ hints .append (COMPILE_COMMANDS_HINT )
161+ if _looks_like_msvc_error (output ) and MSVC_HINT not in output :
162+ hints .append (MSVC_HINT )
163+ if not hints :
164+ return output
165+ separator = "\n \n " if output .rstrip ("\n " ) else ""
166+ return output .rstrip ("\n " ) + separator + "\n \n " .join (hints )
167+
168+
93169def _exec_clang_tidy (command ) -> Tuple [int , str ]:
94170 """Run clang-tidy and return (retval, output)."""
95171 try :
96172 sp = subprocess .run (
97173 command , stdout = subprocess .PIPE , stderr = subprocess .PIPE , encoding = "utf-8"
98174 )
99175 output = (sp .stdout or "" ) + (sp .stderr or "" )
176+ output = _append_guidance (output )
100177 retval = (
101178 1 if sp .returncode != 0 or "warning:" in output or "error:" in output else 0
102179 )
@@ -139,8 +216,11 @@ def run_file(source_file: str) -> Tuple[int, str]:
139216
140217def run_clang_tidy (args = None ) -> Tuple [int , str ]:
141218 hook_args , other_args = parser .parse_known_args (args )
142- if hook_args .version :
143- resolve_install ("clang-tidy" , hook_args .version )
219+ _ , version_error = resolve_install_with_diagnostics (
220+ "clang-tidy" , hook_args .version , hook_args .verbose
221+ )
222+ if version_error is not None :
223+ return 1 , version_error
144224
145225 compile_db_path , error = _resolve_compile_db (hook_args , other_args )
146226 if error is not None :
@@ -152,6 +232,10 @@ def run_clang_tidy(args=None) -> Tuple[int, str]:
152232 f"Using compile_commands.json from: { compile_db_path } " , file = sys .stderr
153233 )
154234 other_args = ["-p" , compile_db_path ] + other_args
235+ elif hook_args .verbose and not hook_args .no_compile_commands :
236+ has_p = any (a == "-p" or a .startswith ("-p=" ) for a in other_args )
237+ if not has_p :
238+ print (_compile_commands_not_found_message (), file = sys .stderr )
155239
156240 clang_tidy_args , source_files = _split_source_files (other_args )
157241
0 commit comments