xref: /aosp_15_r20/external/grpc-grpc/tools/distrib/run_clang_tidy.py (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
1#!/usr/bin/env python3
2# Copyright 2017 gRPC authors.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import argparse
17import multiprocessing
18import os
19import subprocess
20import sys
21
22sys.path.append(
23    os.path.join(
24        os.path.dirname(sys.argv[0]), "..", "run_tests", "python_utils"
25    )
26)
27import jobset
28
29clang_tidy = os.environ.get("CLANG_TIDY", "clang-tidy")
30
31argp = argparse.ArgumentParser(description="Run clang-tidy against core")
32argp.add_argument("files", nargs="+", help="Files to tidy")
33argp.add_argument("--fix", dest="fix", action="store_true")
34argp.add_argument(
35    "-j",
36    "--jobs",
37    type=int,
38    default=multiprocessing.cpu_count(),
39    help="Number of CPUs to use",
40)
41argp.add_argument("--only-changed", dest="only_changed", action="store_true")
42argp.set_defaults(fix=False, only_changed=False)
43args = argp.parse_args()
44
45# Explicitly passing the .clang-tidy config by reading it.
46# This is required because source files in the compilation database are
47# in a different source tree so clang-tidy cannot find the right config file
48# by seeking their parent directories.
49with open(".clang-tidy") as f:
50    config = f.read()
51cmdline = [
52    clang_tidy,
53    "--config=" + config,
54]
55
56if args.fix:
57    cmdline.append("--fix-errors")
58
59if args.only_changed:
60    orig_files = set(args.files)
61    actual_files = []
62    output = subprocess.check_output(
63        ["git", "diff", "upstream/master", "HEAD", "--name-only"]
64    )
65    for line in output.decode("ascii").splitlines(False):
66        if line in orig_files:
67            print(("check: %s" % line))
68            actual_files.append(line)
69        else:
70            print(("skip: %s - not in the build" % line))
71    args.files = actual_files
72
73jobs = []
74for filename in args.files:
75    jobs.append(
76        jobset.JobSpec(
77            cmdline + [filename],
78            shortname=filename,
79            timeout_seconds=15 * 60,
80        )
81    )
82
83num_fails, res_set = jobset.run(jobs, maxjobs=args.jobs, quiet_success=True)
84sys.exit(num_fails)
85