compilation_db.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. """
  2. Implements the ability for SCons to emit a compilation database for the MongoDB project. See
  3. http://clang.llvm.org/docs/JSONCompilationDatabase.html for details on what a compilation
  4. database is, and why you might want one. The only user visible entry point here is
  5. 'env.CompilationDatabase'. This method takes an optional 'target' to name the file that
  6. should hold the compilation database, otherwise, the file defaults to compile_commands.json,
  7. which is the name that most clang tools search for by default.
  8. """
  9. # Copyright 2020 MongoDB Inc.
  10. #
  11. # Permission is hereby granted, free of charge, to any person obtaining
  12. # a copy of this software and associated documentation files (the
  13. # "Software"), to deal in the Software without restriction, including
  14. # without limitation the rights to use, copy, modify, merge, publish,
  15. # distribute, sublicense, and/or sell copies of the Software, and to
  16. # permit persons to whom the Software is furnished to do so, subject to
  17. # the following conditions:
  18. #
  19. # The above copyright notice and this permission notice shall be included
  20. # in all copies or substantial portions of the Software.
  21. #
  22. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
  23. # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  24. # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  25. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  26. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  27. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  28. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  29. #
  30. import json
  31. import itertools
  32. import fnmatch
  33. import SCons
  34. from SCons.Tool.cxx import CXXSuffixes
  35. from SCons.Tool.cc import CSuffixes
  36. from SCons.Tool.asm import ASSuffixes, ASPPSuffixes
  37. # TODO: Is there a better way to do this than this global? Right now this exists so that the
  38. # emitter we add can record all of the things it emits, so that the scanner for the top level
  39. # compilation database can access the complete list, and also so that the writer has easy
  40. # access to write all of the files. But it seems clunky. How can the emitter and the scanner
  41. # communicate more gracefully?
  42. __COMPILATION_DB_ENTRIES = []
  43. # We make no effort to avoid rebuilding the entries. Someday, perhaps we could and even
  44. # integrate with the cache, but there doesn't seem to be much call for it.
  45. class __CompilationDbNode(SCons.Node.Python.Value):
  46. def __init__(self, value):
  47. SCons.Node.Python.Value.__init__(self, value)
  48. self.Decider(changed_since_last_build_node)
  49. def changed_since_last_build_node(child, target, prev_ni, node):
  50. """Dummy decider to force always building"""
  51. return True
  52. def make_emit_compilation_DB_entry(comstr):
  53. """
  54. Effectively this creates a lambda function to capture:
  55. * command line
  56. * source
  57. * target
  58. :param comstr: unevaluated command line
  59. :return: an emitter which has captured the above
  60. """
  61. user_action = SCons.Action.Action(comstr)
  62. def emit_compilation_db_entry(target, source, env):
  63. """
  64. This emitter will be added to each c/c++ object build to capture the info needed
  65. for clang tools
  66. :param target: target node(s)
  67. :param source: source node(s)
  68. :param env: Environment for use building this node
  69. :return: target(s), source(s)
  70. """
  71. dbtarget = __CompilationDbNode(source)
  72. entry = env.__COMPILATIONDB_Entry(
  73. target=dbtarget,
  74. source=[],
  75. __COMPILATIONDB_UOUTPUT=target,
  76. __COMPILATIONDB_USOURCE=source,
  77. __COMPILATIONDB_UACTION=user_action,
  78. __COMPILATIONDB_ENV=env,
  79. )
  80. # TODO: Technically, these next two lines should not be required: it should be fine to
  81. # cache the entries. However, they don't seem to update properly. Since they are quick
  82. # to re-generate disable caching and sidestep this problem.
  83. env.AlwaysBuild(entry)
  84. env.NoCache(entry)
  85. __COMPILATION_DB_ENTRIES.append(dbtarget)
  86. return target, source
  87. return emit_compilation_db_entry
  88. def compilation_db_entry_action(target, source, env, **kw):
  89. """
  90. Create a dictionary with evaluated command line, target, source
  91. and store that info as an attribute on the target
  92. (Which has been stored in __COMPILATION_DB_ENTRIES array
  93. :param target: target node(s)
  94. :param source: source node(s)
  95. :param env: Environment for use building this node
  96. :param kw:
  97. :return: None
  98. """
  99. command = env["__COMPILATIONDB_UACTION"].strfunction(
  100. target=env["__COMPILATIONDB_UOUTPUT"],
  101. source=env["__COMPILATIONDB_USOURCE"],
  102. env=env["__COMPILATIONDB_ENV"],
  103. )
  104. entry = {
  105. "directory": env.Dir("#").abspath,
  106. "command": command,
  107. "file": env["__COMPILATIONDB_USOURCE"][0],
  108. "output": env["__COMPILATIONDB_UOUTPUT"][0],
  109. }
  110. target[0].write(entry)
  111. def write_compilation_db(target, source, env):
  112. entries = []
  113. use_abspath = env["COMPILATIONDB_USE_ABSPATH"] in [True, 1, "True", "true"]
  114. use_path_filter = env.subst("$COMPILATIONDB_PATH_FILTER")
  115. use_srcpath_filter = env.subst("$COMPILATIONDB_SRCPATH_FILTER")
  116. for s in __COMPILATION_DB_ENTRIES:
  117. entry = s.read()
  118. source_file = entry["file"]
  119. output_file = entry["output"]
  120. if source_file.rfile().srcnode().exists():
  121. source_file = source_file.rfile().srcnode()
  122. if use_abspath:
  123. source_file = source_file.abspath
  124. output_file = output_file.abspath
  125. else:
  126. source_file = source_file.path
  127. output_file = output_file.path
  128. # print("output_file, path_filter", output_file, use_path_filter)
  129. if use_path_filter and not fnmatch.fnmatch(output_file, use_path_filter):
  130. continue
  131. if use_srcpath_filter and not fnmatch.fnmatch(source_file, use_srcpath_filter):
  132. continue
  133. path_entry = {
  134. "directory": entry["directory"],
  135. "command": entry["command"],
  136. "file": source_file,
  137. "output": output_file,
  138. }
  139. entries.append(path_entry)
  140. with open(target[0].path, "w") as output_file:
  141. json.dump(
  142. entries, output_file, sort_keys=True, indent=4, separators=(",", ": ")
  143. )
  144. def scan_compilation_db(node, env, path):
  145. return __COMPILATION_DB_ENTRIES
  146. def compilation_db_emitter(target, source, env):
  147. """fix up the source/targets"""
  148. # Someone called env.CompilationDatabase('my_targetname.json')
  149. if not target and len(source) == 1:
  150. target = source
  151. # Default target name is compilation_db.json
  152. if not target:
  153. target = [
  154. "compile_commands.json",
  155. ]
  156. # No source should have been passed. Drop it.
  157. if source:
  158. source = []
  159. return target, source
  160. def generate(env, **kwargs):
  161. static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
  162. env.SetDefault(
  163. COMPILATIONDB_COMSTR=kwargs.get(
  164. "COMPILATIONDB_COMSTR", "Building compilation database $TARGET"
  165. ),
  166. COMPILATIONDB_USE_ABSPATH=False,
  167. COMPILATIONDB_PATH_FILTER="",
  168. COMPILATIONDB_SRCPATH_FILTER="",
  169. )
  170. components_by_suffix = itertools.chain(
  171. itertools.product(
  172. CSuffixes,
  173. [
  174. (static_obj, SCons.Defaults.StaticObjectEmitter, "$CCCOM"),
  175. (shared_obj, SCons.Defaults.SharedObjectEmitter, "$SHCCCOM"),
  176. ],
  177. ),
  178. itertools.product(
  179. CXXSuffixes,
  180. [
  181. (static_obj, SCons.Defaults.StaticObjectEmitter, "$CXXCOM"),
  182. (shared_obj, SCons.Defaults.SharedObjectEmitter, "$SHCXXCOM"),
  183. ],
  184. ),
  185. itertools.product(
  186. ASSuffixes,
  187. [(static_obj, SCons.Defaults.StaticObjectEmitter, "$ASCOM")],
  188. [(shared_obj, SCons.Defaults.SharedObjectEmitter, "$ASCOM")],
  189. ),
  190. itertools.product(
  191. ASPPSuffixes,
  192. [(static_obj, SCons.Defaults.StaticObjectEmitter, "$ASPPCOM")],
  193. [(shared_obj, SCons.Defaults.SharedObjectEmitter, "$ASPPCOM")],
  194. ),
  195. )
  196. for entry in components_by_suffix:
  197. suffix = entry[0]
  198. builder, base_emitter, command = entry[1]
  199. # Assumes a dictionary emitter
  200. emitter = builder.emitter.get(suffix, False)
  201. if emitter:
  202. # We may not have tools installed which initialize all or any of
  203. # cxx, cc, or assembly. If not skip resetting the respective emitter.
  204. builder.emitter[suffix] = SCons.Builder.ListEmitter(
  205. [
  206. emitter,
  207. make_emit_compilation_DB_entry(command),
  208. ]
  209. )
  210. env.Append(
  211. BUILDERS={
  212. "__COMPILATIONDB_Entry": SCons.Builder.Builder(
  213. action=SCons.Action.Action(compilation_db_entry_action, None),
  214. ),
  215. "CompilationDatabase": SCons.Builder.Builder(
  216. action=SCons.Action.Action(
  217. write_compilation_db, "$COMPILATIONDB_COMSTR"
  218. ),
  219. target_scanner=SCons.Scanner.Scanner(
  220. function=scan_compilation_db, node_class=None
  221. ),
  222. emitter=compilation_db_emitter,
  223. suffix="json",
  224. ),
  225. }
  226. )
  227. def exists(env):
  228. return True