mirror of https://github.com/mongodb/mongo
166 lines
6.4 KiB
Python
Executable File
166 lines
6.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Runs clang-tidy in parallel and combines the the results for easier viewing."""
|
|
|
|
import argparse
|
|
import datetime
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
import queue
|
|
import time
|
|
from typing import Any, Dict, List, Optional
|
|
import multiprocessing
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
files_to_tidy = queue.SimpleQueue()
|
|
files_to_parse = queue.SimpleQueue()
|
|
|
|
|
|
def _clang_tidy_executor(clang_tidy_binary: str, clang_tidy_cfg: Dict[str, Any], output_dir: str):
|
|
while True:
|
|
clang_tidy_filename: Optional[Path] = files_to_tidy.get()
|
|
if clang_tidy_filename is None:
|
|
files_to_parse.put(None)
|
|
files_to_tidy.put(None)
|
|
break
|
|
|
|
print(f"Running clang-tidy on {clang_tidy_filename}")
|
|
clang_tidy_parent_dir = output_dir / clang_tidy_filename.parent
|
|
os.makedirs(clang_tidy_parent_dir, exist_ok=True)
|
|
|
|
output_filename_base = clang_tidy_parent_dir / clang_tidy_filename.name
|
|
output_filename_fixes = output_filename_base.with_suffix(".yml")
|
|
clang_tidy_command = [
|
|
clang_tidy_binary, clang_tidy_filename, f"-export-fixes={output_filename_fixes}",
|
|
f"-config={json.dumps(clang_tidy_cfg)}"
|
|
]
|
|
proc = subprocess.run(clang_tidy_command, capture_output=True, check=False)
|
|
if proc.returncode != 0:
|
|
output_filename_out = output_filename_base.with_suffix(".fail")
|
|
files_to_parse.put(output_filename_fixes)
|
|
print(
|
|
f"Running clang-tidy on {clang_tidy_filename} had errors see {output_filename_out}")
|
|
else:
|
|
output_filename_out = output_filename_base.with_suffix(".pass")
|
|
print(f"Running clang-tidy on {clang_tidy_filename} had no errors")
|
|
|
|
with open(output_filename_out, 'wb') as output:
|
|
output.write(proc.stderr)
|
|
output.write(proc.stdout)
|
|
|
|
|
|
def _combine_errors(clang_tidy_executors: int, fixes_filename: str) -> int:
|
|
failed_files = 0
|
|
all_fixes = {}
|
|
while clang_tidy_executors > 0:
|
|
item = files_to_parse.get()
|
|
|
|
# Once all running threads say they are done we want to exit
|
|
if item is None:
|
|
clang_tidy_executors -= 1
|
|
continue
|
|
|
|
failed_files += 1
|
|
|
|
# Read the yaml fixes for the file to combine them with the other suggested fixes
|
|
with open(item) as input_yml:
|
|
fixes = yaml.safe_load(input_yml)
|
|
for fix in fixes['Diagnostics']:
|
|
fix_data = all_fixes.setdefault(fix["DiagnosticName"], {}).setdefault(
|
|
fix["FilePath"], {}).setdefault(
|
|
fix["FileOffset"], {
|
|
"replacements": fix["Replacements"], "message": fix["Message"], "count": 0,
|
|
"source_files": []
|
|
})
|
|
fix_data["count"] += 1
|
|
fix_data["source_files"].append(fixes['MainSourceFile'])
|
|
with open(fixes_filename, "w") as files_file:
|
|
json.dump(all_fixes, files_file, indent=4, sort_keys=True)
|
|
|
|
return failed_files
|
|
|
|
|
|
def _report_status(total_jobs: int, clang_tidy_executor_threads: List[threading.Thread]):
|
|
start_time = time.time()
|
|
running_jobs = 1
|
|
while running_jobs > 0:
|
|
time.sleep(5)
|
|
pretty_time_duration = str(datetime.timedelta(seconds=time.time() - start_time))
|
|
running_jobs = sum(
|
|
[1 for t in clang_tidy_executor_threads if t.is_alive()]) # Count threads running a job
|
|
# files_to_tidy contains a None which can be ignored
|
|
print(
|
|
f"There are {running_jobs} active jobs. The number of jobs queued is {files_to_tidy.qsize()-1}/{total_jobs}. Duration {pretty_time_duration}."
|
|
)
|
|
|
|
|
|
def main():
|
|
"""Execute Main entry point."""
|
|
|
|
parser = argparse.ArgumentParser(description='Run multithreaded clang-tidy')
|
|
|
|
parser.add_argument('-t', "--threads", type=int, default=multiprocessing.cpu_count(),
|
|
help="Run with a specific number of threads")
|
|
parser.add_argument("-d", "--output-dir", type=str, default="clang_tidy_fixes",
|
|
help="Directory to write all clang-tidy output to")
|
|
parser.add_argument("-o", "--fixes-file", type=str, default="clang_tidy_fixes.json",
|
|
help="Report json file to write combined fixes to")
|
|
parser.add_argument("-c", "--compile-commands", type=str, default="compile_commands.json",
|
|
help="compile_commands.json file to use to find the files to tidy")
|
|
# TODO: Is there someway to get this without hardcoding this much
|
|
parser.add_argument("-y", "--clang-tidy-toolchain", type=str, default="v3")
|
|
parser.add_argument("-f", "--clang-tidy-cfg", type=str, default=".clang-tidy")
|
|
args = parser.parse_args()
|
|
|
|
clang_tidy_binary = f'/opt/mongodbtoolchain/{args.clang_tidy_toolchain}/bin/clang-tidy'
|
|
|
|
with open(args.compile_commands) as compile_commands:
|
|
compile_commands = json.load(compile_commands)
|
|
|
|
with open(args.clang_tidy_cfg) as clang_tidy_cfg:
|
|
clang_tidy_cfg = yaml.safe_load(clang_tidy_cfg)
|
|
|
|
for file_doc in compile_commands:
|
|
# A few special cases of files to ignore
|
|
if not "src/mongo" in file_doc["file"]:
|
|
continue
|
|
# TODO SERVER-49884 Remove this when we no longer check in generated Bison.
|
|
if "parser_gen.cpp" in file_doc["file"]:
|
|
continue
|
|
files_to_tidy.put(Path(file_doc["file"]))
|
|
|
|
total_jobs = files_to_tidy.qsize()
|
|
files_to_tidy.put(None)
|
|
workers = args.threads
|
|
|
|
clang_tidy_executor_threads: List[threading.Thread] = []
|
|
for _ in range(workers):
|
|
clang_tidy_executor_threads.append(
|
|
threading.Thread(target=_clang_tidy_executor, args=(clang_tidy_binary, clang_tidy_cfg,
|
|
args.output_dir)))
|
|
clang_tidy_executor_threads[-1].start()
|
|
|
|
report_status_thread = threading.Thread(target=_report_status,
|
|
args=(total_jobs, clang_tidy_executor_threads))
|
|
report_status_thread.start()
|
|
|
|
failed_files = _combine_errors(workers, Path(args.output_dir, args.fixes_file))
|
|
|
|
# Join all threads
|
|
report_status_thread.join()
|
|
for thread in clang_tidy_executor_threads:
|
|
thread.join()
|
|
|
|
# Zip up all the files for upload
|
|
subprocess.run(["tar", "-czvf", args.output_dir + ".tgz", args.output_dir], check=False)
|
|
|
|
return failed_files
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|