| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- """
- Implements the ability for SCons to emit a compilation database for the MongoDB project. See
- http://clang.llvm.org/docs/JSONCompilationDatabase.html for details on what a compilation
- database is, and why you might want one. The only user visible entry point here is
- 'env.CompilationDatabase'. This method takes an optional 'target' to name the file that
- should hold the compilation database, otherwise, the file defaults to compile_commands.json,
- which is the name that most clang tools search for by default.
- """
- # Copyright 2020 MongoDB Inc.
- #
- # Permission is hereby granted, free of charge, to any person obtaining
- # a copy of this software and associated documentation files (the
- # "Software"), to deal in the Software without restriction, including
- # without limitation the rights to use, copy, modify, merge, publish,
- # distribute, sublicense, and/or sell copies of the Software, and to
- # permit persons to whom the Software is furnished to do so, subject to
- # the following conditions:
- #
- # The above copyright notice and this permission notice shall be included
- # in all copies or substantial portions of the Software.
- #
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
- # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
- # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- #
- import json
- import itertools
- import fnmatch
- import SCons
- from SCons.Tool.cxx import CXXSuffixes
- from SCons.Tool.cc import CSuffixes
- from SCons.Tool.asm import ASSuffixes, ASPPSuffixes
- # TODO: Is there a better way to do this than this global? Right now this exists so that the
- # emitter we add can record all of the things it emits, so that the scanner for the top level
- # compilation database can access the complete list, and also so that the writer has easy
- # access to write all of the files. But it seems clunky. How can the emitter and the scanner
- # communicate more gracefully?
- __COMPILATION_DB_ENTRIES = []
- # We make no effort to avoid rebuilding the entries. Someday, perhaps we could and even
- # integrate with the cache, but there doesn't seem to be much call for it.
- class __CompilationDbNode(SCons.Node.Python.Value):
- def __init__(self, value):
- SCons.Node.Python.Value.__init__(self, value)
- self.Decider(changed_since_last_build_node)
- def changed_since_last_build_node(child, target, prev_ni, node):
- """Dummy decider to force always building"""
- return True
- def make_emit_compilation_DB_entry(comstr):
- """
- Effectively this creates a lambda function to capture:
- * command line
- * source
- * target
- :param comstr: unevaluated command line
- :return: an emitter which has captured the above
- """
- user_action = SCons.Action.Action(comstr)
- def emit_compilation_db_entry(target, source, env):
- """
- This emitter will be added to each c/c++ object build to capture the info needed
- for clang tools
- :param target: target node(s)
- :param source: source node(s)
- :param env: Environment for use building this node
- :return: target(s), source(s)
- """
- dbtarget = __CompilationDbNode(source)
- entry = env.__COMPILATIONDB_Entry(
- target=dbtarget,
- source=[],
- __COMPILATIONDB_UOUTPUT=target,
- __COMPILATIONDB_USOURCE=source,
- __COMPILATIONDB_UACTION=user_action,
- __COMPILATIONDB_ENV=env,
- )
- # TODO: Technically, these next two lines should not be required: it should be fine to
- # cache the entries. However, they don't seem to update properly. Since they are quick
- # to re-generate disable caching and sidestep this problem.
- env.AlwaysBuild(entry)
- env.NoCache(entry)
- __COMPILATION_DB_ENTRIES.append(dbtarget)
- return target, source
- return emit_compilation_db_entry
- def compilation_db_entry_action(target, source, env, **kw):
- """
- Create a dictionary with evaluated command line, target, source
- and store that info as an attribute on the target
- (Which has been stored in __COMPILATION_DB_ENTRIES array
- :param target: target node(s)
- :param source: source node(s)
- :param env: Environment for use building this node
- :param kw:
- :return: None
- """
- command = env["__COMPILATIONDB_UACTION"].strfunction(
- target=env["__COMPILATIONDB_UOUTPUT"],
- source=env["__COMPILATIONDB_USOURCE"],
- env=env["__COMPILATIONDB_ENV"],
- )
- entry = {
- "directory": env.Dir("#").abspath,
- "command": command,
- "file": env["__COMPILATIONDB_USOURCE"][0],
- "output": env["__COMPILATIONDB_UOUTPUT"][0],
- }
- target[0].write(entry)
- def write_compilation_db(target, source, env):
- entries = []
- use_abspath = env["COMPILATIONDB_USE_ABSPATH"] in [True, 1, "True", "true"]
- use_path_filter = env.subst("$COMPILATIONDB_PATH_FILTER")
- use_srcpath_filter = env.subst("$COMPILATIONDB_SRCPATH_FILTER")
- for s in __COMPILATION_DB_ENTRIES:
- entry = s.read()
- source_file = entry["file"]
- output_file = entry["output"]
- if source_file.rfile().srcnode().exists():
- source_file = source_file.rfile().srcnode()
- if use_abspath:
- source_file = source_file.abspath
- output_file = output_file.abspath
- else:
- source_file = source_file.path
- output_file = output_file.path
- # print("output_file, path_filter", output_file, use_path_filter)
- if use_path_filter and not fnmatch.fnmatch(output_file, use_path_filter):
- continue
- if use_srcpath_filter and not fnmatch.fnmatch(source_file, use_srcpath_filter):
- continue
- path_entry = {
- "directory": entry["directory"],
- "command": entry["command"],
- "file": source_file,
- "output": output_file,
- }
- entries.append(path_entry)
- with open(target[0].path, "w") as output_file:
- json.dump(
- entries, output_file, sort_keys=True, indent=4, separators=(",", ": ")
- )
- def scan_compilation_db(node, env, path):
- return __COMPILATION_DB_ENTRIES
- def compilation_db_emitter(target, source, env):
- """fix up the source/targets"""
- # Someone called env.CompilationDatabase('my_targetname.json')
- if not target and len(source) == 1:
- target = source
- # Default target name is compilation_db.json
- if not target:
- target = [
- "compile_commands.json",
- ]
- # No source should have been passed. Drop it.
- if source:
- source = []
- return target, source
- def generate(env, **kwargs):
- static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
- env.SetDefault(
- COMPILATIONDB_COMSTR=kwargs.get(
- "COMPILATIONDB_COMSTR", "Building compilation database $TARGET"
- ),
- COMPILATIONDB_USE_ABSPATH=False,
- COMPILATIONDB_PATH_FILTER="",
- COMPILATIONDB_SRCPATH_FILTER="",
- )
- components_by_suffix = itertools.chain(
- itertools.product(
- CSuffixes,
- [
- (static_obj, SCons.Defaults.StaticObjectEmitter, "$CCCOM"),
- (shared_obj, SCons.Defaults.SharedObjectEmitter, "$SHCCCOM"),
- ],
- ),
- itertools.product(
- CXXSuffixes,
- [
- (static_obj, SCons.Defaults.StaticObjectEmitter, "$CXXCOM"),
- (shared_obj, SCons.Defaults.SharedObjectEmitter, "$SHCXXCOM"),
- ],
- ),
- itertools.product(
- ASSuffixes,
- [(static_obj, SCons.Defaults.StaticObjectEmitter, "$ASCOM")],
- [(shared_obj, SCons.Defaults.SharedObjectEmitter, "$ASCOM")],
- ),
- itertools.product(
- ASPPSuffixes,
- [(static_obj, SCons.Defaults.StaticObjectEmitter, "$ASPPCOM")],
- [(shared_obj, SCons.Defaults.SharedObjectEmitter, "$ASPPCOM")],
- ),
- )
- for entry in components_by_suffix:
- suffix = entry[0]
- builder, base_emitter, command = entry[1]
- # Assumes a dictionary emitter
- emitter = builder.emitter.get(suffix, False)
- if emitter:
- # We may not have tools installed which initialize all or any of
- # cxx, cc, or assembly. If not skip resetting the respective emitter.
- builder.emitter[suffix] = SCons.Builder.ListEmitter(
- [
- emitter,
- make_emit_compilation_DB_entry(command),
- ]
- )
- env.Append(
- BUILDERS={
- "__COMPILATIONDB_Entry": SCons.Builder.Builder(
- action=SCons.Action.Action(compilation_db_entry_action, None),
- ),
- "CompilationDatabase": SCons.Builder.Builder(
- action=SCons.Action.Action(
- write_compilation_db, "$COMPILATIONDB_COMSTR"
- ),
- target_scanner=SCons.Scanner.Scanner(
- function=scan_compilation_db, node_class=None
- ),
- emitter=compilation_db_emitter,
- suffix="json",
- ),
- }
- )
- def exists(env):
- return True
|