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