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