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