gen_compilation_database.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. #!/usr/bin/env python3
  2. # Copyright 2020 gRPC authors.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. # This is based on the script on the Envoy project
  16. # https://github.com/envoyproxy/envoy/blob/master/tools/gen_compilation_database.py
  17. import argparse
  18. import glob
  19. import json
  20. import logging
  21. import os
  22. from pathlib import Path
  23. import re
  24. import shlex
  25. import subprocess
  26. RE_INCLUDE_SYSTEM = re.compile("\s*-I\s+/usr/[^ ]+")
  27. # This method is equivalent to https://github.com/grailbio/bazel-compilation-database/blob/master/generate.sh
  28. def generateCompilationDatabase(args):
  29. # We need to download all remote outputs for generated source code.
  30. # This option lives here to override those specified in bazelrc.
  31. bazel_options = shlex.split(os.environ.get("BAZEL_BUILD_OPTIONS", "")) + [
  32. "--config=compdb",
  33. "--remote_download_outputs=all",
  34. ]
  35. subprocess.check_call(["bazel", "build"] + bazel_options + [
  36. "--aspects=@bazel_compdb//:aspects.bzl%compilation_database_aspect",
  37. "--output_groups=compdb_files,header_files"
  38. ] + args.bazel_targets)
  39. execroot = subprocess.check_output(["bazel", "info", "execution_root"] +
  40. bazel_options).decode().strip()
  41. compdb = []
  42. for compdb_file in Path(execroot).glob("**/*.compile_commands.json"):
  43. compdb.extend(
  44. json.loads(
  45. "[" +
  46. compdb_file.read_text().replace("__EXEC_ROOT__", execroot) +
  47. "]"))
  48. if args.dedup_targets:
  49. compdb_map = {target["file"]: target for target in compdb}
  50. compdb = list(compdb_map.values())
  51. return compdb
  52. def isHeader(filename):
  53. for ext in (".h", ".hh", ".hpp", ".hxx"):
  54. if filename.endswith(ext):
  55. return True
  56. return False
  57. def isCompileTarget(target, args):
  58. filename = target["file"]
  59. if not args.include_headers and isHeader(filename):
  60. return False
  61. if not args.include_genfiles:
  62. if filename.startswith("bazel-out/"):
  63. return False
  64. if not args.include_external:
  65. if filename.startswith("external/"):
  66. return False
  67. return True
  68. def modifyCompileCommand(target, args):
  69. cc, options = target["command"].split(" ", 1)
  70. # Workaround for bazel added C++11 options, those doesn't affect build itself but
  71. # clang-tidy will misinterpret them.
  72. options = options.replace("-std=c++0x ", "")
  73. options = options.replace("-std=c++11 ", "")
  74. if args.vscode:
  75. # Visual Studio Code doesn't seem to like "-iquote". Replace it with
  76. # old-style "-I".
  77. options = options.replace("-iquote ", "-I ")
  78. if args.ignore_system_headers:
  79. # Remove all include options for /usr/* directories
  80. options = RE_INCLUDE_SYSTEM.sub("", options)
  81. if isHeader(target["file"]):
  82. options += " -Wno-pragma-once-outside-header -Wno-unused-const-variable"
  83. options += " -Wno-unused-function"
  84. if not target["file"].startswith("external/"):
  85. # *.h file is treated as C header by default while our headers files are all C++11.
  86. options = "-x c++ -std=c++11 -fexceptions " + options
  87. target["command"] = " ".join([cc, options])
  88. return target
  89. def fixCompilationDatabase(args, db):
  90. db = [
  91. modifyCompileCommand(target, args)
  92. for target in db
  93. if isCompileTarget(target, args)
  94. ]
  95. with open("compile_commands.json", "w") as db_file:
  96. json.dump(db, db_file, indent=2)
  97. if __name__ == "__main__":
  98. parser = argparse.ArgumentParser(
  99. description='Generate JSON compilation database')
  100. parser.add_argument('--include_external', action='store_true')
  101. parser.add_argument('--include_genfiles', action='store_true')
  102. parser.add_argument('--include_headers', action='store_true')
  103. parser.add_argument('--vscode', action='store_true')
  104. parser.add_argument('--ignore_system_headers', action='store_true')
  105. parser.add_argument('--dedup_targets', action='store_true')
  106. parser.add_argument('bazel_targets', nargs='*', default=["//..."])
  107. args = parser.parse_args()
  108. fixCompilationDatabase(args, generateCompilationDatabase(args))