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