xref: /aosp_15_r20/external/grpc-grpc/tools/distrib/gen_compilation_database.py (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
1#!/usr/bin/env python3
2
3# Copyright 2020 gRPC authors.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17# This is based on the script on the Envoy project
18# https://github.com/envoyproxy/envoy/blob/master/tools/gen_compilation_database.py
19
20import argparse
21import glob
22import json
23import logging
24import os
25from pathlib import Path
26import re
27import shlex
28import subprocess
29
30RE_INCLUDE_SYSTEM = re.compile("\s*-I\s+/usr/[^ ]+")
31
32
33# This method is equivalent to https://github.com/grailbio/bazel-compilation-database/blob/master/generate.sh
34def generateCompilationDatabase(args):
35    # We need to download all remote outputs for generated source code.
36    # This option lives here to override those specified in bazelrc.
37    bazel_options = shlex.split(os.environ.get("BAZEL_BUILD_OPTIONS", "")) + [
38        "--config=compdb",
39        "--remote_download_outputs=all",
40    ]
41
42    subprocess.check_call(
43        ["bazel", "build"]
44        + bazel_options
45        + [
46            "--aspects=@bazel_compdb//:aspects.bzl%compilation_database_aspect",
47            "--output_groups=compdb_files,header_files",
48        ]
49        + args.bazel_targets
50    )
51
52    execroot = (
53        subprocess.check_output(
54            ["bazel", "info", "execution_root"] + bazel_options
55        )
56        .decode()
57        .strip()
58    )
59
60    compdb = []
61    for compdb_file in Path(execroot).glob("**/*.compile_commands.json"):
62        compdb.extend(
63            json.loads(
64                "["
65                + compdb_file.read_text().replace("__EXEC_ROOT__", execroot)
66                + "]"
67            )
68        )
69
70    if args.dedup_targets:
71        compdb_map = {target["file"]: target for target in compdb}
72        compdb = list(compdb_map.values())
73
74    return compdb
75
76
77def isHeader(filename):
78    for ext in (".h", ".hh", ".hpp", ".hxx"):
79        if filename.endswith(ext):
80            return True
81    return False
82
83
84def isCompileTarget(target, args):
85    filename = target["file"]
86    if not args.include_headers and isHeader(filename):
87        return False
88    if not args.include_genfiles:
89        if filename.startswith("bazel-out/"):
90            return False
91    if not args.include_external:
92        if filename.startswith("external/"):
93            return False
94    return True
95
96
97def modifyCompileCommand(target, args):
98    cc, options = target["command"].split(" ", 1)
99
100    # Workaround for bazel added C++14 options, those doesn't affect build itself but
101    # clang-tidy will misinterpret them.
102    options = options.replace("-std=c++0x ", "")
103    options = options.replace("-std=c++14 ", "")
104
105    # Add -DNDEBUG so that editors show the correct size information for structs.
106    options += " -DNDEBUG"
107
108    if args.vscode:
109        # Visual Studio Code doesn't seem to like "-iquote". Replace it with
110        # old-style "-I".
111        options = options.replace("-iquote ", "-I ")
112
113    if args.ignore_system_headers:
114        # Remove all include options for /usr/* directories
115        options = RE_INCLUDE_SYSTEM.sub("", options)
116
117    if isHeader(target["file"]):
118        options += " -Wno-pragma-once-outside-header -Wno-unused-const-variable"
119        options += " -Wno-unused-function"
120        if not target["file"].startswith("external/"):
121            # *.h file is treated as C header by default while our headers files are all C++14.
122            options = "-x c++ -std=c++14 -fexceptions " + options
123
124    target["command"] = " ".join([cc, options])
125    return target
126
127
128def fixCompilationDatabase(args, db):
129    db = [
130        modifyCompileCommand(target, args)
131        for target in db
132        if isCompileTarget(target, args)
133    ]
134
135    with open("compile_commands.json", "w") as db_file:
136        json.dump(db, db_file, indent=2)
137
138
139if __name__ == "__main__":
140    parser = argparse.ArgumentParser(
141        description="Generate JSON compilation database"
142    )
143    parser.add_argument("--include_external", action="store_true")
144    parser.add_argument("--include_genfiles", action="store_true")
145    parser.add_argument("--include_headers", action="store_true")
146    parser.add_argument("--vscode", action="store_true")
147    parser.add_argument("--ignore_system_headers", action="store_true")
148    parser.add_argument("--dedup_targets", action="store_true")
149    parser.add_argument("bazel_targets", nargs="*", default=["//..."])
150    args = parser.parse_args()
151    fixCompilationDatabase(args, generateCompilationDatabase(args))
152