22``clang_tools.main``
33--------------------
44
5- The module containing main entrypoint function.
5+ The module containing the unified main entrypoint function.
6+ Supports both binary (static build) and wheel-based installation
7+ via a single ``clang-tools`` command.
68"""
79
810import argparse
11+ import sys
912
1013from .install import install_clang_tools , uninstall_clang_tools
11- from . import RESET_COLOR , YELLOW
14+ from . import RESET_COLOR , YELLOW , MIN_VERSION , MAX_VERSION
1215from .util import Version
1316
1417
18+ def _is_version_like (target : str ) -> bool :
19+ """Check if *target* looks like a version number (e.g. ``"18"``, ``"18.1"``).
20+ """
21+ try :
22+ parts = target .split ("." )
23+ for p in parts :
24+ int (p )
25+ return True
26+ except ValueError :
27+ return False
28+
29+
30+ #: Known tool names supported by wheel installs
31+ WHEEL_TOOLS = {"clang-format" , "clang-tidy" , "clang-query" , "clang-apply-replacements" }
32+
33+
34+ def _wheel_install (tools : list [str ], version : str | None ) -> int :
35+ """Install tool(s) via wheel (cpp_linter_hooks).
36+
37+ :returns: exit code (0 on success, 1 on failure).
38+ """
39+ from cpp_linter_hooks .util import resolve_install # lazy import
40+
41+ ok = True
42+ for tool in tools :
43+ path = resolve_install (tool , version )
44+ version_str = f" version { version } " if version else " latest version"
45+ if path :
46+ print (f"{ tool } { version_str } installed at: { path } " )
47+ else :
48+ print (f"Failed to install { tool } { version_str } " , file = sys .stderr )
49+ ok = False
50+ return 0 if ok else 1
51+
52+
1553def get_parser () -> argparse .ArgumentParser :
16- """Get and parser to interpret CLI args."""
17- parser = argparse .ArgumentParser ()
54+ """Get a parser to interpret CLI args (unified entry point)."""
55+ parser = argparse .ArgumentParser (
56+ description = "Install and manage clang-tools (clang-format, clang-tidy, etc.)"
57+ )
1858
59+ # ------------------------------------------------------------------
60+ # Backward-compatible top-level flags (deprecated – kept for
61+ # compatibility with older scripts / workflows)
62+ # ------------------------------------------------------------------
1963 parser .add_argument (
2064 "-i" ,
2165 "--install" ,
2266 metavar = "VERSION" ,
23- help = "Install clang-tools about a specific version. This can be in the form of"
24- " a semantic version specification (``x.y.z``, ``x.y``, ``x``). NOTE: A "
25- "malformed version specification will cause a silent failure." ,
67+ help = "(deprecated) Use 'clang-tools install <version>' instead" ,
68+ dest = "_legacy_install" ,
69+ )
70+ parser .add_argument (
71+ "-u" ,
72+ "--uninstall" ,
73+ metavar = "VERSION" ,
74+ help = "(deprecated) Use 'clang-tools uninstall <version>' instead" ,
75+ dest = "_legacy_uninstall" ,
2676 )
2777 parser .add_argument (
2878 "-t" ,
@@ -43,54 +93,236 @@ def get_parser() -> argparse.ArgumentParser:
4393 "-f" ,
4494 "--overwrite" ,
4595 action = "store_true" ,
46- help = "Force overwriting the symlink to the installed binary. This will only "
47- "overwrite an existing symlink." ,
96+ help = "Force overwriting the symlink to the installed binary." ,
4897 )
4998 parser .add_argument (
5099 "-b" ,
51100 "--no-progress-bar" ,
52101 action = "store_true" ,
53102 help = "Do not display a progress bar for downloads." ,
54103 )
55- parser .add_argument (
56- "-u" ,
57- "--uninstall" ,
58- metavar = "VERSION" ,
59- help = "Uninstall clang-tools with specific version. "
60- "This is done before any install." ,
104+
105+ # ------------------------------------------------------------------
106+ # Subcommands (new-style CLI)
107+ # ------------------------------------------------------------------
108+ subparsers = parser .add_subparsers (dest = "command" , help = "Available commands" )
109+
110+ # --- ``clang-tools install <target>`` ---------------------------------
111+ install_p = subparsers .add_parser ("install" , help = "Install clang-tools" )
112+ install_p .add_argument (
113+ "target" ,
114+ help = "Version number (e.g. 18) or tool name (e.g. clang-format)" ,
115+ )
116+ install_p .add_argument (
117+ "--binary" ,
118+ action = "store_true" ,
119+ help = "Force binary (static build) installation" ,
120+ )
121+ install_p .add_argument (
122+ "--wheel" ,
123+ action = "store_true" ,
124+ help = "Force wheel installation via cpp-linter-hooks" ,
125+ )
126+ install_p .add_argument (
127+ "--version" ,
128+ dest = "explicit_version" ,
129+ default = None ,
130+ metavar = "VER" ,
131+ help = "Explicit version for wheel install (when target is a tool name)" ,
132+ )
133+ install_p .add_argument (
134+ "-t" ,
135+ "--tool" ,
136+ nargs = "+" ,
137+ default = ["clang-format" , "clang-tidy" ],
138+ metavar = "TOOL" ,
139+ help = "Specify which tool(s) to install." ,
140+ )
141+ install_p .add_argument (
142+ "-d" ,
143+ "--directory" ,
144+ default = "" ,
145+ metavar = "DIR" ,
146+ help = "The directory where the clang-tools are installed." ,
61147 )
148+ install_p .add_argument (
149+ "-f" ,
150+ "--overwrite" ,
151+ action = "store_true" ,
152+ help = "Force overwriting the symlink to the installed binary." ,
153+ )
154+ install_p .add_argument (
155+ "-b" ,
156+ "--no-progress-bar" ,
157+ action = "store_true" ,
158+ help = "Do not display a progress bar for downloads." ,
159+ )
160+
161+ # --- ``clang-tools uninstall <version>`` ------------------------------
162+ uninstall_p = subparsers .add_parser ("uninstall" , help = "Uninstall clang-tools" )
163+ uninstall_p .add_argument ("version" , help = "Version to uninstall" )
164+ uninstall_p .add_argument (
165+ "-t" ,
166+ "--tool" ,
167+ nargs = "+" ,
168+ default = ["clang-format" , "clang-tidy" ],
169+ metavar = "TOOL" ,
170+ help = "Specify which tool(s) to uninstall." ,
171+ )
172+ uninstall_p .add_argument (
173+ "-d" ,
174+ "--directory" ,
175+ default = "" ,
176+ metavar = "DIR" ,
177+ help = "The directory from which to uninstall the tools." ,
178+ )
179+
62180 return parser
63181
64182
65- def main ():
66- """The main entrypoint to the CLI program."""
183+ def main () -> int :
184+ """Unified entry point for the CLI program.
185+
186+ :returns: exit code (0 on success, 1 on failure).
187+ """
67188 parser = get_parser ()
68189 args = parser .parse_args ()
69190
70- if args .uninstall :
71- uninstall_clang_tools (args .uninstall , args .tool , args .directory )
72- elif args .install :
73- version = Version (args .install )
74- if version .info != (0 , 0 , 0 ):
75- install_clang_tools (
76- version ,
77- args .tool ,
78- args .directory ,
79- args .overwrite ,
80- args .no_progress_bar ,
81- )
82- else :
83- print (
84- f"{ YELLOW } The version specified is not a semantic" ,
85- f"specification{ RESET_COLOR } " ,
191+ # ---- Legacy backward-compat path ----------------------------------
192+ if args ._legacy_uninstall or args ._legacy_install :
193+ if args ._legacy_uninstall :
194+ uninstall_clang_tools (
195+ args ._legacy_uninstall , args .tool , args .directory
86196 )
87- else :
197+ if args ._legacy_install :
198+ version = Version (args ._legacy_install )
199+ if version .info != (0 , 0 , 0 ):
200+ install_clang_tools (
201+ version ,
202+ args .tool ,
203+ args .directory ,
204+ args .overwrite ,
205+ args .no_progress_bar ,
206+ )
207+ else :
208+ print (
209+ f"{ YELLOW } The version specified is not a semantic" ,
210+ f"specification{ RESET_COLOR } " ,
211+ file = sys .stderr ,
212+ )
213+ return 1
214+ return 0
215+
216+ # ---- No subcommand, no legacy flags → nothing to do --------------
217+ if args .command is None :
88218 print (
89- f"{ YELLOW } Nothing to do because `--install` and `--uninstall`" ,
90- f"was not specified.{ RESET_COLOR } " ,
219+ f"{ YELLOW } Nothing to do. Use 'install' or 'uninstall' subcommand,"
220+ f" or --install/--uninstall flags.{ RESET_COLOR } " ,
221+ file = sys .stderr ,
91222 )
92223 parser .print_help ()
224+ return 0
225+
226+ # ---- ``uninstall`` subcommand ------------------------------------
227+ if args .command == "uninstall" :
228+ uninstall_clang_tools (args .version , args .tool , args .directory )
229+ return 0
230+
231+ # ---- ``install`` subcommand --------------------------------------
232+ if args .command == "install" :
233+ target : str = args .target
234+ binary : bool = args .binary
235+ wheel : bool = args .wheel
236+
237+ # safety: --binary and --wheel are mutually exclusive
238+ if binary and wheel :
239+ print (
240+ f"{ YELLOW } Error: --binary and --wheel are mutually"
241+ f" exclusive{ RESET_COLOR } " ,
242+ file = sys .stderr ,
243+ )
244+ return 1
245+
246+ # ---- Case: --wheel (target may be tool name or version) -----
247+ if wheel :
248+ if _is_version_like (target ):
249+ # ``clang-tools install 18 --wheel``
250+ return _wheel_install (args .tool , target )
251+ else :
252+ # ``clang-tools install clang-format --wheel``
253+ # Install the target as the tool (ignore default --tool)
254+ return _wheel_install ([target ], args .explicit_version )
255+
256+ # ---- Case: --binary (target must be a version) --------------
257+ if binary :
258+ if not _is_version_like (target ):
259+ print (
260+ f"{ YELLOW } Error: --binary requires a version number"
261+ f" (got '{ target } '){ RESET_COLOR } " ,
262+ file = sys .stderr ,
263+ )
264+ return 1
265+ version = Version (target )
266+ if version .info != (0 , 0 , 0 ):
267+ install_clang_tools (
268+ version ,
269+ args .tool ,
270+ args .directory ,
271+ args .overwrite ,
272+ args .no_progress_bar ,
273+ )
274+ return 0
275+ print (
276+ f"{ YELLOW } The version specified is not a semantic"
277+ f" specification{ RESET_COLOR } " ,
278+ file = sys .stderr ,
279+ )
280+ return 1
281+
282+ # ---- Auto-detect (no --binary or --wheel flag) --------------
283+ if _is_version_like (target ):
284+ version = Version (target )
285+ if version .info != (0 , 0 , 0 ):
286+ # try binary first, fall back to wheel
287+ try :
288+ install_clang_tools (
289+ version ,
290+ args .tool ,
291+ args .directory ,
292+ args .overwrite ,
293+ args .no_progress_bar ,
294+ )
295+ return 0
296+ except Exception as exc :
297+ print (
298+ f"{ YELLOW } Binary install failed"
299+ f" ({ exc } ), falling back to"
300+ f" wheel...{ RESET_COLOR } " ,
301+ file = sys .stderr ,
302+ )
303+ # fallback to wheel
304+ return _wheel_install (args .tool , target )
305+ else :
306+ print (
307+ f"{ YELLOW } The version specified is not a semantic"
308+ f" specification{ RESET_COLOR } " ,
309+ file = sys .stderr ,
310+ )
311+ return 1
312+ else :
313+ # target is a tool name → install via wheel
314+ if target not in WHEEL_TOOLS :
315+ print (
316+ f"{ YELLOW } Unknown target '{ target } '. Expected a"
317+ f" version number or one of: "
318+ f"{ ', ' .join (sorted (WHEEL_TOOLS ))} { RESET_COLOR } " ,
319+ file = sys .stderr ,
320+ )
321+ return 1
322+ return _wheel_install ([target ], args .explicit_version )
323+
324+ return 0 # unreachable
93325
94326
95327if __name__ == "__main__" :
96- main ()
328+ raise SystemExit ( main () )
0 commit comments