3 # Copyright 2020 gRPC authors.
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
17 # This is based on the script on the Envoy project
18 # https://github.com/envoyproxy/envoy/blob/master/tools/gen_compilation_database.py
28 from pathlib import Path
30 RE_INCLUDE_SYSTEM = re.compile("\s*-I\s+/usr/[^ ]+")
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", "")) + [
39 "--remote_download_outputs=all",
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)
47 execroot = subprocess.check_output(["bazel", "info", "execution_root"] +
48 bazel_options).decode().strip()
51 for compdb_file in Path(execroot).glob("**/*.compile_commands.json"):
55 compdb_file.read_text().replace("__EXEC_ROOT__", execroot) +
58 if args.dedup_targets:
59 compdb_map = {target["file"]: target for target in compdb}
60 compdb = list(compdb_map.values())
65 def isHeader(filename):
66 for ext in (".h", ".hh", ".hpp", ".hxx"):
67 if filename.endswith(ext):
72 def isCompileTarget(target, args):
73 filename = target["file"]
74 if not args.include_headers and isHeader(filename):
76 if not args.include_genfiles:
77 if filename.startswith("bazel-out/"):
79 if not args.include_external:
80 if filename.startswith("external/"):
85 def modifyCompileCommand(target, args):
86 cc, options = target["command"].split(" ", 1)
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 ", "")
94 # Visual Studio Code doesn't seem to like "-iquote". Replace it with
96 options = options.replace("-iquote ", "-I ")
98 if args.ignore_system_headers:
99 # Remove all include options for /usr/* directories
100 options = RE_INCLUDE_SYSTEM.sub("", options)
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
109 target["command"] = " ".join([cc, options])
113 def fixCompilationDatabase(args, db):
115 modifyCompileCommand(target, args)
117 if isCompileTarget(target, args)
120 with open("compile_commands.json", "w") as db_file:
121 json.dump(db, db_file, indent=2)
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))