• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
25import re
26import shlex
27import subprocess
28from pathlib import Path
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(["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
65def isHeader(filename):
66    for ext in (".h", ".hh", ".hpp", ".hxx"):
67        if filename.endswith(ext):
68            return True
69    return False
70
71
72def 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
85def 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
113def 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
124if __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))
136