clang-tools 23.0.0git
run-clang-tidy.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2#
3# ===-----------------------------------------------------------------------===#
4#
5# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
6# See https://llvm.org/LICENSE.txt for license information.
7# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8#
9# ===-----------------------------------------------------------------------===#
10# FIXME: Integrate with clang-tidy-diff.py
11
12
13"""
14Parallel clang-tidy runner
15==========================
16
17Runs clang-tidy over all files in a compilation database. Requires clang-tidy
18and clang-apply-replacements in $PATH.
19
20Example invocations.
21- Run clang-tidy on all files in the current working directory with a default
22 set of checks and show warnings in the cpp files and all project headers.
23 run-clang-tidy.py $PWD
24
25- Fix all header guards.
26 run-clang-tidy.py -fix -checks=-*,llvm-header-guard
27
28- Fix all header guards included from clang-tidy and header guards
29 for clang-tidy headers.
30 run-clang-tidy.py -fix -checks=-*,llvm-header-guard extra/clang-tidy \
31 -header-filter=extra/clang-tidy
32
33Compilation database setup:
34https://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
35"""
36
37import argparse
38import asyncio
39from dataclasses import dataclass
40import glob
41import json
42import multiprocessing
43import os
44import re
45import shlex
46import shutil
47import subprocess
48import sys
49import tempfile
50import time
51import traceback
52from types import ModuleType
53from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple, TypeVar
54
55
56yaml: Optional[ModuleType] = None
57try:
58 import yaml
59except ImportError:
60 yaml = None
61
62
63def strtobool(val: str) -> bool:
64 """Convert a string representation of truth to a bool following LLVM's CLI argument parsing."""
65
66 val = val.lower()
67 if val in ["", "true", "1"]:
68 return True
69 elif val in ["false", "0"]:
70 return False
71
72 # Return ArgumentTypeError so that argparse does not substitute its own error message
73 raise argparse.ArgumentTypeError(
74 f"'{val}' is invalid value for boolean argument! Try 0 or 1."
75 )
76
77
78def find_compilation_database(path: str) -> str:
79 """Adjusts the directory until a compilation database is found."""
80 result = os.path.realpath("./")
81 while not os.path.isfile(os.path.join(result, path)):
82 parent = os.path.dirname(result)
83 if result == parent:
84 print("Error: could not find compilation database.")
85 sys.exit(1)
86 result = parent
87 return result
88
89
91 f: Optional[str],
92 clang_tidy_binary: str,
93 checks: str,
94 tmpdir: Optional[str],
95 build_path: str,
96 header_filter: Optional[str],
97 allow_enabling_alpha_checkers: bool,
98 extra_arg: List[str],
99 extra_arg_before: List[str],
100 removed_arg: List[str],
101 quiet: bool,
102 config_file_path: str,
103 config: str,
104 line_filter: Optional[str],
105 use_color: bool,
106 plugins: List[str],
107 warnings_as_errors: Optional[str],
108 exclude_header_filter: Optional[str],
109 allow_no_checks: bool,
110 store_check_profile: Optional[str],
111) -> List[str]:
112 """Gets a command line for clang-tidy."""
113 start = [clang_tidy_binary]
114 if allow_enabling_alpha_checkers:
115 start.append("-allow-enabling-analyzer-alpha-checkers")
116 if exclude_header_filter is not None:
117 start.append(f"--exclude-header-filter={exclude_header_filter}")
118 if header_filter is not None:
119 start.append(f"-header-filter={header_filter}")
120 if line_filter is not None:
121 start.append(f"-line-filter={line_filter}")
122 if use_color is not None:
123 if use_color:
124 start.append("--use-color")
125 else:
126 start.append("--use-color=false")
127 if checks:
128 start.append(f"-checks={checks}")
129 if tmpdir is not None:
130 start.append("-export-fixes")
131 # Get a temporary file. We immediately close the handle so clang-tidy can
132 # overwrite it.
133 (handle, name) = tempfile.mkstemp(suffix=".yaml", dir=tmpdir)
134 os.close(handle)
135 start.append(name)
136 for arg in extra_arg:
137 start.append(f"-extra-arg={arg}")
138 for arg in extra_arg_before:
139 start.append(f"-extra-arg-before={arg}")
140 for arg in removed_arg:
141 start.append(f"-removed-arg={arg}")
142 start.append(f"-p={build_path}")
143 if quiet:
144 start.append("-quiet")
145 if config_file_path:
146 start.append(f"--config-file={config_file_path}")
147 elif config:
148 start.append(f"-config={config}")
149 for plugin in plugins:
150 start.append(f"-load={plugin}")
151 if warnings_as_errors:
152 start.append(f"--warnings-as-errors={warnings_as_errors}")
153 if allow_no_checks:
154 start.append("--allow-no-checks")
155 if store_check_profile:
156 start.append("--enable-check-profile")
157 start.append(f"--store-check-profile={store_check_profile}")
158 if f:
159 start.append(f)
160 return start
161
162
163def merge_replacement_files(tmpdir: str, mergefile: str) -> None:
164 """Merge all replacement files in a directory into a single file"""
165 assert yaml
166 # The fixes suggested by clang-tidy >= 4.0.0 are given under
167 # the top level key 'Diagnostics' in the output yaml files
168 mergekey = "Diagnostics"
169 merged = []
170 for replacefile in glob.iglob(os.path.join(tmpdir, "*.yaml")):
171 content = yaml.safe_load(open(replacefile, "r"))
172 if not content:
173 continue # Skip empty files.
174 merged.extend(content.get(mergekey, []))
175
176 if merged:
177 # MainSourceFile: The key is required by the definition inside
178 # include/clang/Tooling/ReplacementsYaml.h, but the value
179 # is actually never used inside clang-apply-replacements,
180 # so we set it to '' here.
181 output = {"MainSourceFile": "", mergekey: merged}
182 with open(mergefile, "w") as out:
183 yaml.safe_dump(output, out)
184 else:
185 # Empty the file:
186 open(mergefile, "w").close()
187
188
189def aggregate_profiles(profile_dir: str) -> Dict[str, float]:
190 """Aggregate timing data from multiple profile JSON files"""
191 aggregated: Dict[str, float] = {}
192
193 for profile_file in glob.iglob(os.path.join(profile_dir, "*.json")):
194 try:
195 with open(profile_file, "r", encoding="utf-8") as f:
196 data = json.load(f)
197 profile_data: Dict[str, float] = data.get("profile", {})
198
199 for key, value in profile_data.items():
200 if key.startswith("time.clang-tidy."):
201 if key in aggregated:
202 aggregated[key] += value
203 else:
204 aggregated[key] = value
205 except (json.JSONDecodeError, KeyError, IOError) as e:
206 print(f"Error: invalid json file {profile_file}: {e}", file=sys.stderr)
207 continue
208
209 return aggregated
210
211
212def print_profile_data(aggregated_data: Dict[str, float]) -> None:
213 """Print aggregated checks profile data in the same format as clang-tidy"""
214 if not aggregated_data:
215 return
216
217 # Extract checker names and their timing data
218 checkers: Dict[str, Dict[str, float]] = {}
219 for key, value in aggregated_data.items():
220 parts = key.split(".")
221 if len(parts) >= 4 and parts[0] == "time" and parts[1] == "clang-tidy":
222 checker_name = ".".join(
223 parts[2:-1]
224 ) # Everything between "clang-tidy" and the timing type
225 timing_type = parts[-1] # wall, user, or sys
226
227 if checker_name not in checkers:
228 checkers[checker_name] = {"wall": 0.0, "user": 0.0, "sys": 0.0}
229
230 checkers[checker_name][timing_type] = value
231
232 if not checkers:
233 return
234
235 total_user = sum(data["user"] for data in checkers.values())
236 total_sys = sum(data["sys"] for data in checkers.values())
237 total_wall = sum(data["wall"] for data in checkers.values())
238
239 sorted_checkers: List[Tuple[str, Dict[str, float]]] = sorted(
240 checkers.items(), key=lambda x: x[1]["user"] + x[1]["sys"], reverse=True
241 )
242
243 def print_stderr(*args: Any, **kwargs: Any) -> None:
244 print(*args, file=sys.stderr, **kwargs)
245
246 print_stderr(
247 "===-------------------------------------------------------------------------==="
248 )
249 print_stderr(" clang-tidy checks profiling")
250 print_stderr(
251 "===-------------------------------------------------------------------------==="
252 )
253 print_stderr(
254 f" Total Execution Time: {total_user + total_sys:.4f} seconds ({total_wall:.4f} wall clock)\n"
255 )
256
257 # Calculate field widths based on the Total line which has the largest values
258 total_combined = total_user + total_sys
259 user_width = len(f"{total_user:.4f}")
260 sys_width = len(f"{total_sys:.4f}")
261 combined_width = len(f"{total_combined:.4f}")
262 wall_width = len(f"{total_wall:.4f}")
263
264 # Header with proper alignment
265 additional_width = 9 # for " (100.0%)"
266 user_header = "---User Time---".center(user_width + additional_width)
267 sys_header = "--System Time--".center(sys_width + additional_width)
268 combined_header = "--User+System--".center(combined_width + additional_width)
269 wall_header = "---Wall Time---".center(wall_width + additional_width)
270
271 print_stderr(
272 f" {user_header} {sys_header} {combined_header} {wall_header} --- Name ---"
273 )
274
275 for checker_name, data in sorted_checkers:
276 user_time = data["user"]
277 sys_time = data["sys"]
278 wall_time = data["wall"]
279 combined_time = user_time + sys_time
280
281 user_percent = (user_time / total_user * 100) if total_user > 0 else 0
282 sys_percent = (sys_time / total_sys * 100) if total_sys > 0 else 0
283 combined_percent = (
284 (combined_time / total_combined * 100) if total_combined > 0 else 0
285 )
286 wall_percent = (wall_time / total_wall * 100) if total_wall > 0 else 0
287
288 user_str = f"{user_time:{user_width}.4f} ({user_percent:5.1f}%)"
289 sys_str = f"{sys_time:{sys_width}.4f} ({sys_percent:5.1f}%)"
290 combined_str = f"{combined_time:{combined_width}.4f} ({combined_percent:5.1f}%)"
291 wall_str = f"{wall_time:{wall_width}.4f} ({wall_percent:5.1f}%)"
292
293 print_stderr(
294 f" {user_str} {sys_str} {combined_str} {wall_str} {checker_name}"
295 )
296
297 user_total_str = f"{total_user:{user_width}.4f} (100.0%)"
298 sys_total_str = f"{total_sys:{sys_width}.4f} (100.0%)"
299 combined_total_str = f"{total_combined:{combined_width}.4f} (100.0%)"
300 wall_total_str = f"{total_wall:{wall_width}.4f} (100.0%)"
301
302 print_stderr(
303 f" {user_total_str} {sys_total_str} {combined_total_str} {wall_total_str} Total"
304 )
305
306
307def find_binary(arg: str, name: str, build_path: str) -> str:
308 """Get the path for a binary or exit"""
309 if arg:
310 if shutil.which(arg):
311 return arg
312 else:
313 raise SystemExit(
314 f"error: passed binary '{arg}' was not found or is not executable"
315 )
316
317 built_path = os.path.join(build_path, "bin", name)
318 binary = shutil.which(name) or shutil.which(built_path)
319 if binary:
320 return binary
321 else:
322 raise SystemExit(f"error: failed to find {name} in $PATH or at {built_path}")
323
324
326 args: argparse.Namespace, clang_apply_replacements_binary: str, tmpdir: str
327) -> None:
328 """Calls clang-apply-fixes on a given directory."""
329 invocation = [clang_apply_replacements_binary]
330 invocation.append("-ignore-insert-conflict")
331 if args.format:
332 invocation.append("-format")
333 if args.style:
334 invocation.append(f"-style={args.style}")
335 invocation.append(tmpdir)
336 subprocess.call(invocation)
337
338
339# FIXME Python 3.12: This can be simplified out with run_with_semaphore[T](...).
340T = TypeVar("T")
341
342
344 semaphore: asyncio.Semaphore,
345 f: Callable[..., Awaitable[T]],
346 *args: Any,
347 **kwargs: Any,
348) -> T:
349 async with semaphore:
350 return await f(*args, **kwargs)
351
352
353@dataclass
355 filename: str
356 invocation: List[str]
357 returncode: int
358 stdout: str
359 stderr: str
360 elapsed: float
361
362
363async def run_tidy(
364 args: argparse.Namespace,
365 name: str,
366 clang_tidy_binary: str,
367 tmpdir: str,
368 build_path: str,
369 store_check_profile: Optional[str],
370) -> ClangTidyResult:
371 """
372 Runs clang-tidy on a single file and returns the result.
373 """
374 invocation = get_tidy_invocation(
375 name,
376 clang_tidy_binary,
377 args.checks,
378 tmpdir,
379 build_path,
380 args.header_filter,
381 args.allow_enabling_alpha_checkers,
382 args.extra_arg,
383 args.extra_arg_before,
384 args.removed_arg,
385 args.quiet,
386 args.config_file,
387 args.config,
388 args.line_filter,
389 args.use_color,
390 args.plugins,
391 args.warnings_as_errors,
392 args.exclude_header_filter,
393 args.allow_no_checks,
394 store_check_profile,
395 )
396
397 try:
398 process = await asyncio.create_subprocess_exec(
399 *invocation, stdout=subprocess.PIPE, stderr=subprocess.PIPE
400 )
401 start = time.time()
402 stdout, stderr = await process.communicate()
403 end = time.time()
404 except asyncio.CancelledError:
405 process.terminate()
406 await process.wait()
407 raise
408
409 assert process.returncode is not None
410 return ClangTidyResult(
411 name,
412 invocation,
413 process.returncode,
414 stdout.decode("UTF-8"),
415 stderr.decode("UTF-8"),
416 end - start,
417 )
418
419
420async def main() -> None:
421 parser = argparse.ArgumentParser(
422 description="Runs clang-tidy over all files "
423 "in a compilation database. Requires "
424 "clang-tidy and clang-apply-replacements in "
425 "$PATH or in your build directory."
426 )
427 parser.add_argument(
428 "-allow-enabling-alpha-checkers",
429 action="store_true",
430 help="Allow alpha checkers from clang-analyzer.",
431 )
432 parser.add_argument(
433 "-clang-tidy-binary", metavar="PATH", help="Path to clang-tidy binary."
434 )
435 parser.add_argument(
436 "-clang-apply-replacements-binary",
437 metavar="PATH",
438 help="Path to clang-apply-replacements binary.",
439 )
440 parser.add_argument(
441 "-checks",
442 default=None,
443 help="Checks filter, when not specified, use clang-tidy default.",
444 )
445 config_group = parser.add_mutually_exclusive_group()
446 config_group.add_argument(
447 "-config",
448 default=None,
449 help="Specifies a configuration in YAML/JSON format: "
450 " -config=\"{Checks: '*', "
451 ' CheckOptions: {x: y}}" '
452 "When the value is empty, clang-tidy will "
453 "attempt to find a file named .clang-tidy for "
454 "each source file in its parent directories.",
455 )
456 config_group.add_argument(
457 "-config-file",
458 default=None,
459 help="Specify the path of .clang-tidy or custom config "
460 "file: e.g. -config-file=/some/path/myTidyConfigFile. "
461 "This option internally works exactly the same way as "
462 "-config option after reading specified config file. "
463 "Use either -config-file or -config, not both.",
464 )
465 parser.add_argument(
466 "-exclude-header-filter",
467 default=None,
468 help="Regular expression matching the names of the "
469 "headers to exclude diagnostics from. Diagnostics from "
470 "the main file of each translation unit are always "
471 "displayed.",
472 )
473 parser.add_argument(
474 "-header-filter",
475 default=None,
476 help="Regular expression matching the names of the "
477 "headers to output diagnostics from. Diagnostics from "
478 "the main file of each translation unit are always "
479 "displayed.",
480 )
481 parser.add_argument(
482 "-source-filter",
483 default=None,
484 help="Regular expression matching the names of the "
485 "source files from compilation database to output "
486 "diagnostics from.",
487 )
488 parser.add_argument(
489 "-line-filter",
490 default=None,
491 help="List of files and line ranges to output diagnostics from.",
492 )
493 if yaml:
494 parser.add_argument(
495 "-export-fixes",
496 metavar="file_or_directory",
497 dest="export_fixes",
498 help="A directory or a yaml file to store suggested fixes in, "
499 "which can be applied with clang-apply-replacements. If the "
500 "parameter is a directory, the fixes of each compilation unit are "
501 "stored in individual yaml files in the directory.",
502 )
503 else:
504 parser.add_argument(
505 "-export-fixes",
506 metavar="directory",
507 dest="export_fixes",
508 help="A directory to store suggested fixes in, which can be applied "
509 "with clang-apply-replacements. The fixes of each compilation unit are "
510 "stored in individual yaml files in the directory.",
511 )
512 parser.add_argument(
513 "-j",
514 type=int,
515 default=0,
516 help="Number of tidy instances to be run in parallel.",
517 )
518 parser.add_argument(
519 "files",
520 nargs="*",
521 default=[".*"],
522 help="Files to be processed (regex on path).",
523 )
524 parser.add_argument("-fix", action="store_true", help="apply fix-its.")
525 parser.add_argument(
526 "-format", action="store_true", help="Reformat code after applying fixes."
527 )
528 parser.add_argument(
529 "-style",
530 default="file",
531 help="The style of reformat code after applying fixes.",
532 )
533 parser.add_argument(
534 "-use-color",
535 type=strtobool,
536 nargs="?",
537 const=True,
538 help="Use colors in diagnostics, overriding clang-tidy's"
539 " default behavior. This option overrides the 'UseColor"
540 "' option in .clang-tidy file, if any.",
541 )
542 parser.add_argument(
543 "-p", dest="build_path", help="Path used to read a compile command database."
544 )
545 parser.add_argument(
546 "-extra-arg",
547 dest="extra_arg",
548 action="append",
549 default=[],
550 help="Additional argument to append to the compiler command line.",
551 )
552 parser.add_argument(
553 "-extra-arg-before",
554 dest="extra_arg_before",
555 action="append",
556 default=[],
557 help="Additional argument to prepend to the compiler command line.",
558 )
559 parser.add_argument(
560 "-removed-arg",
561 dest="removed_arg",
562 action="append",
563 default=[],
564 help="Arguments to remove from the compiler command line.",
565 )
566 parser.add_argument(
567 "-quiet", action="store_true", help="Run clang-tidy in quiet mode."
568 )
569 parser.add_argument(
570 "-load",
571 dest="plugins",
572 action="append",
573 default=[],
574 help="Load the specified plugin in clang-tidy.",
575 )
576 parser.add_argument(
577 "-warnings-as-errors",
578 default=None,
579 help="Upgrades warnings to errors. Same format as '-checks'.",
580 )
581 parser.add_argument(
582 "-allow-no-checks",
583 action="store_true",
584 help="Allow empty enabled checks.",
585 )
586 parser.add_argument(
587 "-enable-check-profile",
588 action="store_true",
589 help="Enable per-check timing profiles, and print a report",
590 )
591 parser.add_argument(
592 "-hide-progress",
593 action="store_true",
594 help="Hide progress",
595 )
596 args = parser.parse_args()
597
598 db_path = "compile_commands.json"
599
600 if args.build_path is not None:
601 build_path = args.build_path
602 else:
603 # Find our database
604 build_path = find_compilation_database(db_path)
605
606 clang_tidy_binary = find_binary(args.clang_tidy_binary, "clang-tidy", build_path)
607
608 if args.fix:
609 clang_apply_replacements_binary = find_binary(
610 args.clang_apply_replacements_binary, "clang-apply-replacements", build_path
611 )
612
613 combine_fixes = False
614 export_fixes_dir: Optional[str] = None
615 delete_fixes_dir = False
616 if args.export_fixes is not None:
617 # if a directory is given, create it if it does not exist
618 if args.export_fixes.endswith(os.path.sep) and not os.path.isdir(
619 args.export_fixes
620 ):
621 os.makedirs(args.export_fixes)
622
623 if not os.path.isdir(args.export_fixes):
624 if not yaml:
625 raise RuntimeError(
626 "Cannot combine fixes in one yaml file. Either install PyYAML or specify an output directory."
627 )
628
629 combine_fixes = True
630
631 if os.path.isdir(args.export_fixes):
632 export_fixes_dir = args.export_fixes
633
634 if export_fixes_dir is None and (args.fix or combine_fixes):
635 export_fixes_dir = tempfile.mkdtemp()
636 delete_fixes_dir = True
637
638 profile_dir: Optional[str] = None
639 if args.enable_check_profile:
640 profile_dir = tempfile.mkdtemp()
641
642 try:
643 invocation = get_tidy_invocation(
644 None,
645 clang_tidy_binary,
646 args.checks,
647 None,
648 build_path,
649 args.header_filter,
650 args.allow_enabling_alpha_checkers,
651 args.extra_arg,
652 args.extra_arg_before,
653 args.removed_arg,
654 args.quiet,
655 args.config_file,
656 args.config,
657 args.line_filter,
658 args.use_color,
659 args.plugins,
660 args.warnings_as_errors,
661 args.exclude_header_filter,
662 args.allow_no_checks,
663 None, # No profiling for the list-checks invocation
664 )
665 invocation.append("-list-checks")
666 invocation.append("-")
667 # Even with -quiet we still want to check if we can call clang-tidy.
668 subprocess.check_call(
669 invocation, stdout=subprocess.DEVNULL if args.quiet else None
670 )
671 except Exception:
672 print("Unable to run clang-tidy.", file=sys.stderr)
673 sys.exit(1)
674
675 # Load the database and extract all files.
676 with open(os.path.join(build_path, db_path)) as f:
677 database = json.load(f)
678 files = {os.path.abspath(os.path.join(e["directory"], e["file"])) for e in database}
679 number_files_in_database = len(files)
680
681 # Filter source files from compilation database.
682 if args.source_filter:
683 try:
684 source_filter_re = re.compile(args.source_filter)
685 except Exception:
686 print(
687 "Error: unable to compile regex from arg -source-filter:",
688 file=sys.stderr,
689 )
690 traceback.print_exc()
691 sys.exit(1)
692 files = {f for f in files if source_filter_re.match(f)}
693
694 max_task = args.j
695 if max_task == 0:
696 max_task = multiprocessing.cpu_count()
697
698 # Build up a big regexy filter from all command line arguments.
699 file_name_re = re.compile("|".join(args.files))
700 files = {f for f in files if file_name_re.search(f)}
701
702 if not args.hide_progress:
703 print(
704 f"Running clang-tidy in {max_task} threads for {len(files)} files "
705 f"out of {number_files_in_database} in compilation database ..."
706 )
707
708 returncode = 0
709 semaphore = asyncio.Semaphore(max_task)
710 tasks = [
711 asyncio.create_task(
713 semaphore,
714 run_tidy,
715 args,
716 f,
717 clang_tidy_binary,
718 export_fixes_dir,
719 build_path,
720 profile_dir,
721 )
722 )
723 for f in files
724 ]
725
726 try:
727 for i, coro in enumerate(asyncio.as_completed(tasks)):
728 result = await coro
729 if result.returncode != 0:
730 returncode = 1
731 if result.returncode < 0:
732 result.stderr += f"{result.filename}: terminated by signal {-result.returncode}\n"
733 progress = f"[{i + 1: >{len(f'{len(files)}')}}/{len(files)}]"
734 runtime = f"[{result.elapsed:.1f}s]"
735 if not args.hide_progress:
736 print(f"{progress}{runtime} {shlex.join(result.invocation)}")
737 if result.stdout:
738 print(result.stdout, end=("" if result.stderr else "\n"))
739 if result.stderr:
740 print(result.stderr)
741 except asyncio.CancelledError:
742 if not args.hide_progress:
743 print("\nCtrl-C detected, goodbye.")
744 for task in tasks:
745 task.cancel()
746 if delete_fixes_dir:
747 assert export_fixes_dir
748 shutil.rmtree(export_fixes_dir)
749 if profile_dir:
750 shutil.rmtree(profile_dir)
751 return
752
753 if args.enable_check_profile and profile_dir:
754 # Ensure all clang-tidy stdout is flushed before printing profiling
755 sys.stdout.flush()
756 aggregated_data = aggregate_profiles(profile_dir)
757 if aggregated_data:
758 print_profile_data(aggregated_data)
759 else:
760 print("No profiling data found.")
761
762 if combine_fixes:
763 if not args.hide_progress:
764 print(f"Writing fixes to {args.export_fixes} ...")
765 try:
766 assert export_fixes_dir
767 merge_replacement_files(export_fixes_dir, args.export_fixes)
768 except Exception:
769 print("Error exporting fixes.\n", file=sys.stderr)
770 traceback.print_exc()
771 returncode = 1
772
773 if args.fix:
774 if not args.hide_progress:
775 print("Applying fixes ...")
776 try:
777 assert export_fixes_dir
778 apply_fixes(args, clang_apply_replacements_binary, export_fixes_dir)
779 except Exception:
780 print("Error applying fixes.\n", file=sys.stderr)
781 traceback.print_exc()
782 returncode = 1
783
784 if delete_fixes_dir:
785 assert export_fixes_dir
786 shutil.rmtree(export_fixes_dir)
787 if profile_dir:
788 shutil.rmtree(profile_dir)
789 sys.exit(returncode)
790
791
792if __name__ == "__main__":
793 try:
794 asyncio.run(main())
795 except KeyboardInterrupt:
796 pass
ClangTidyResult run_tidy(argparse.Namespace args, str name, str clang_tidy_binary, str tmpdir, str build_path, Optional[str] store_check_profile)
bool strtobool(str val)
Dict[str, float] aggregate_profiles(str profile_dir)
str find_compilation_database(str path)
str find_binary(str arg, str name, str build_path)
List[str] get_tidy_invocation(Optional[str] f, str clang_tidy_binary, str checks, Optional[str] tmpdir, str build_path, Optional[str] header_filter, bool allow_enabling_alpha_checkers, List[str] extra_arg, List[str] extra_arg_before, List[str] removed_arg, bool quiet, str config_file_path, str config, Optional[str] line_filter, bool use_color, List[str] plugins, Optional[str] warnings_as_errors, Optional[str] exclude_header_filter, bool allow_no_checks, Optional[str] store_check_profile)
None print_profile_data(Dict[str, float] aggregated_data)
None apply_fixes(argparse.Namespace args, str clang_apply_replacements_binary, str tmpdir)
None merge_replacement_files(str tmpdir, str mergefile)
T run_with_semaphore(asyncio.Semaphore semaphore, Callable[..., Awaitable[T]] f, *Any args, **Any kwargs)