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