#!/usr/bin/env vpython3 # Copyright (c) 2025 The WebRTC project authors. All Rights Reserved. # # Use of this source code is governed by a BSD-style license # that can be found in the LICENSE file in the root of the source # tree. An additional intellectual property rights grant can be found # in the file PATENTS. All contributing project authors may # be found in the AUTHORS file in the root of the source tree. # # Run clang-tidy in the webrtc source directory. # The list of checks that is getting applied is in the toplevel # .clang-tidy file. To add a new check, add it to that file and run # the script. # # clang-tidy needs to be added to the .gclient file: # Example .gclient file: # solutions = [ # { # "name": "src", # "url": "https://webrtc.googlesource.com/src.git", # "deps_file": "DEPS", # "managed": False, # "custom_deps": {}, # "custom_vars" : { # "checkout_clangd": True, # "checkout_clang_tidy": True, # } # }, # ] # # See also # https://chromium.googlesource.com/chromium/src/+/main/docs/clang_tidy.md import argparse import time import pathlib import subprocess _DEFAULT_WORKDIR = pathlib.Path("out/Default") # This is relative to src dir. _TIDY_BUILD = "tools/clang/scripts/build_clang_tools_extra.py" # These are relative to the work dir so the path needs to be constructed later. _LLVM = "tools/clang/third_party/llvm/" _TIDY_RUNNER = _LLVM + "clang-tools-extra/clang-tidy/tool/run-clang-tidy.py" _TIDY_BINARY = _LLVM + "build/bin/clang-tidy" _REPLACEMENTS_BINARY = _LLVM + "build/bin/clang-apply-replacements" def _valid_dir(path: str) -> pathlib.Path: """Checks if the given path is an existing dir relative to the current working directory. Args: path: Relative dir path to the current working directory Returns: pathlib.Path object wrapping the dir path Raises: ValueError: If the dir doesn't exist """ pathlib_handle = pathlib.Path(path) if not pathlib_handle.is_dir(): raise ValueError(f"Dir path {pathlib_handle} does not exist!") return pathlib_handle def _build_clang_tools(work_dir: pathlib.Path) -> None: if pathlib.Path(work_dir, _TIDY_RUNNER).exists() and pathlib.Path( work_dir, _TIDY_BINARY).exists() and pathlib.Path( work_dir, _REPLACEMENTS_BINARY).exists(): # Assume that tidy updates at least once every 30 days, and # recompile it if it's more than 30 days old. tidy_binary_path = pathlib.Path(work_dir, _TIDY_BINARY) age_in_seconds = time.time() - tidy_binary_path.stat().st_mtime age_in_days = age_in_seconds / (24 * 60 * 60) if age_in_days < 30: return print("Binary is %d days old - recompiling" % age_in_days) print("Fetching and building clang-tidy") build_clang_tools_cmd = (_TIDY_BUILD, "--fetch", work_dir, "clang-tidy", "clang-apply-replacements") subprocess.run(build_clang_tools_cmd, capture_output=False, text=True, check=True) def _generate_compile_commands(work_dir: pathlib.Path) -> None: """Automatically generates the compile_commands.json file to be used by the include cleaner binary. Args: work_dir: gn out dir where the compile_commands json file exists """ compile_commands_path = work_dir / "compile_commands.json" print("Generating compile commands file...") subprocess.run( ["tools/clang/scripts/generate_compdb.py", "-p", work_dir], stdout=compile_commands_path.open(mode="w+"), check=True, ) def _run_clang_tidy(work_dir: pathlib.Path) -> None: clang_tidy_cmd = (work_dir / _TIDY_RUNNER, "-p", work_dir, "-allow-no-checks", "-clang-tidy-binary", work_dir / _TIDY_BINARY, "-clang-apply-replacements-binary", work_dir / _REPLACEMENTS_BINARY, "-fix") subprocess.run(clang_tidy_cmd, capture_output=False, text=True, check=False) def _parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Runs clang-tidy with a set of rules", formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument( "-w", "--work-dir", type=_valid_dir, default=str(_DEFAULT_WORKDIR), help="Specify the gn workdir", ) return parser.parse_args() def main() -> None: args = _parse_args() _build_clang_tools(args.work_dir) _generate_compile_commands(args.work_dir) _run_clang_tidy(args.work_dir) if __name__ == "__main__": main()