Imported Upstream version 1.33.0
[platform/upstream/grpc.git] / tools / distrib / gen_compilation_database.py
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
20 import argparse
21 import glob
22 import json
23 import logging
24 import os
25 import re
26 import shlex
27 import subprocess
28 from pathlib import Path
29
30 RE_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
34 def 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(["bazel", "build"] + bazel_options + [
43         "--aspects=@bazel_compdb//:aspects.bzl%compilation_database_aspect",
44         "--output_groups=compdb_files,header_files"
45     ] + args.bazel_targets)
46
47     execroot = subprocess.check_output(["bazel", "info", "execution_root"] +
48                                        bazel_options).decode().strip()
49
50     compdb = []
51     for compdb_file in Path(execroot).glob("**/*.compile_commands.json"):
52         compdb.extend(
53             json.loads(
54                 "[" +
55                 compdb_file.read_text().replace("__EXEC_ROOT__", execroot) +
56                 "]"))
57
58     if args.dedup_targets:
59         compdb_map = {target["file"]: target for target in compdb}
60         compdb = list(compdb_map.values())
61
62     return compdb
63
64
65 def isHeader(filename):
66     for ext in (".h", ".hh", ".hpp", ".hxx"):
67         if filename.endswith(ext):
68             return True
69     return False
70
71
72 def isCompileTarget(target, args):
73     filename = target["file"]
74     if not args.include_headers and isHeader(filename):
75         return False
76     if not args.include_genfiles:
77         if filename.startswith("bazel-out/"):
78             return False
79     if not args.include_external:
80         if filename.startswith("external/"):
81             return False
82     return True
83
84
85 def modifyCompileCommand(target, args):
86     cc, options = target["command"].split(" ", 1)
87
88     # Workaround for bazel added C++11 options, those doesn't affect build itself but
89     # clang-tidy will misinterpret them.
90     options = options.replace("-std=c++0x ", "")
91     options = options.replace("-std=c++11 ", "")
92
93     if args.vscode:
94         # Visual Studio Code doesn't seem to like "-iquote". Replace it with
95         # old-style "-I".
96         options = options.replace("-iquote ", "-I ")
97
98     if args.ignore_system_headers:
99         # Remove all include options for /usr/* directories
100         options = RE_INCLUDE_SYSTEM.sub("", options)
101
102     if isHeader(target["file"]):
103         options += " -Wno-pragma-once-outside-header -Wno-unused-const-variable"
104         options += " -Wno-unused-function"
105         if not target["file"].startswith("external/"):
106             # *.h file is treated as C header by default while our headers files are all C++11.
107             options = "-x c++ -std=c++11 -fexceptions " + options
108
109     target["command"] = " ".join([cc, options])
110     return target
111
112
113 def fixCompilationDatabase(args, db):
114     db = [
115         modifyCompileCommand(target, args)
116         for target in db
117         if isCompileTarget(target, args)
118     ]
119
120     with open("compile_commands.json", "w") as db_file:
121         json.dump(db, db_file, indent=2)
122
123
124 if __name__ == "__main__":
125     parser = argparse.ArgumentParser(
126         description='Generate JSON compilation database')
127     parser.add_argument('--include_external', action='store_true')
128     parser.add_argument('--include_genfiles', action='store_true')
129     parser.add_argument('--include_headers', action='store_true')
130     parser.add_argument('--vscode', action='store_true')
131     parser.add_argument('--ignore_system_headers', action='store_true')
132     parser.add_argument('--dedup_targets', action='store_true')
133     parser.add_argument('bazel_targets', nargs='*', default=["//..."])
134     args = parser.parse_args()
135     fixCompilationDatabase(args, generateCompilationDatabase(args))