Belle II Software  release-08-01-10
compilation_db.py
1 # Adapted from
2 # https://github.com/mongodb/mongo/blob/8dd6d4755734ed37c1b98dfdefce3ca6bc65f1f6/site_scons/site_tools/compilation_db.py
3 
4 # Copyright 2015 MongoDB Inc.
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 
18 import json
19 import SCons
20 import itertools
21 
22 # Implements the ability for SCons to emit a compilation database for the MongoDB project. See
23 # http://clang.llvm.org/docs/JSONCompilationDatabase.html for details on what a compilation
24 # database is, and why you might want one. The only user visible entry point here is
25 # 'env.CompilationDatabase'. This method takes an optional 'target' to name the file that
26 # should hold the compilation database, otherwise, the file defaults to compile_commands.json,
27 # which is the name that most clang tools search for by default.
28 
29 # TODO: Is there a better way to do this than this global? Right now this exists so that the
30 # emitter we add can record all of the things it emits, so that the scanner for the top level
31 # compilation database can access the complete list, and also so that the writer has easy
32 # access to write all of the files. But it seems clunky. How can the emitter and the scanner
33 # communicate more gracefully?
34 __COMPILATION_DB_ENTRIES = []
35 
36 # Cribbed from Tool/cc.py and Tool/c++.py. It would be better if
37 # we could obtain this from SCons.
38 _CSuffixes = ['.c']
39 if not SCons.Util.case_sensitive_suffixes('.c', '.C'):
40  _CSuffixes.append('.C')
41 
42 _CXXSuffixes = ['.cpp', '.cc', '.cxx', '.c++', '.C++']
43 if SCons.Util.case_sensitive_suffixes('.c', '.C'):
44  _CXXSuffixes.append('.C')
45 
46 
47 # We make no effort to avoid rebuilding the entries. Someday, perhaps we could and even
48 # integrate with the cache, but there doesn't seem to be much call for it.
49 class __CompilationDbNode(SCons.Node.Python.Value):
50  def __init__(self, value):
51  SCons.Node.Python.Value.__init__(self, value)
52  self.Decider(changed_since_last_build_node)
53 
54 
55 def changed_since_last_build_node(node, target, prev_ni, repo_node=None):
56  """ Dummy decider to force alwasy building"""
57  return True
58 
59 
60 def makeEmitCompilationDbEntry(comstr):
61  """
62  Effectively this creates a lambda function to capture:
63  * command line
64  * source
65  * target
66  :param comstr: unevaluated command line
67  :return: an emitter which has captured the above
68  """
69  user_action = SCons.Action.Action(comstr)
70 
71  def EmitCompilationDbEntry(target, source, env):
72  """
73  This emitter will be added to each c/c++ object build to capture the info needed
74  for clang tools
75  :param target: target node(s)
76  :param source: source node(s)
77  :param env: Environment for use building this node
78  :return: target(s), source(s)
79  """
80 
81  dbtarget = __CompilationDbNode(source)
82 
83  entry = env.__COMPILATIONDB_Entry(
84  target=dbtarget, source=[], __COMPILATIONDB_UTARGET=target,
85  __COMPILATIONDB_USOURCE=source, __COMPILATIONDB_UACTION=user_action,
86  __COMPILATIONDB_ENV=env)
87 
88  # TODO: Technically, these next two lines should not be required: it should be fine to
89  # cache the entries. However, they don't seem to update properly. Since they are quick
90  # to re-generate disable caching and sidestep this problem.
91  env.AlwaysBuild(entry)
92  env.NoCache(entry)
93 
94  __COMPILATION_DB_ENTRIES.append(dbtarget)
95 
96  return target, source
97 
98  return EmitCompilationDbEntry
99 
100 
101 def CompilationDbEntryAction(target, source, env, **kw):
102  """
103  Create a dictionary with evaluated command line, target, source
104  and store that info as an attribute on the target
105  (Which has been stored in __COMPILATION_DB_ENTRIES array
106  :param target: target node(s)
107  :param source: source node(s)
108  :param env: Environment for use building this node
109  :param kw:
110  :return: None
111  """
112 
113  command = env['__COMPILATIONDB_UACTION'].strfunction(
114  target=env['__COMPILATIONDB_UTARGET'],
115  source=env['__COMPILATIONDB_USOURCE'],
116  env=env['__COMPILATIONDB_ENV'],
117  )
118 
119  entry = {
120  "directory": env.Dir('#').abspath, "command": command,
121  "file": str(env['__COMPILATIONDB_USOURCE'][0])
122  }
123 
124  target[0].write(entry)
125 
126 
127 def WriteCompilationDb(target, source, env):
128  entries = []
129 
130  for s in __COMPILATION_DB_ENTRIES:
131  entries.append(s.read())
132 
133  with open(str(target[0]), 'w') as target_file:
134  json.dump(entries, target_file, sort_keys=True, indent=4, separators=(',', ': '))
135 
136 
137 def ScanCompilationDb(node, env, path):
138  return __COMPILATION_DB_ENTRIES
139 
140 
141 def generate(env, **kwargs):
142 
143  static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
144 
145  env['COMPILATIONDB_COMSTR'] = kwargs.get('COMPILATIONDB_COMSTR',
146  'Building compilation database $TARGET')
147 
148  components_by_suffix = itertools.chain(
149  itertools.product(_CSuffixes, [
150  (static_obj, SCons.Defaults.StaticObjectEmitter, '$CCCOM'),
151  (shared_obj, SCons.Defaults.SharedObjectEmitter, '$SHCCCOM'),
152  ]),
153  itertools.product(_CXXSuffixes, [
154  (static_obj, SCons.Defaults.StaticObjectEmitter, '$CXXCOM'),
155  (shared_obj, SCons.Defaults.SharedObjectEmitter, '$SHCXXCOM'),
156  ]),
157  )
158 
159  for entry in components_by_suffix:
160  suffix = entry[0]
161  builder, base_emitter, command = entry[1]
162 
163  # Assumes a dictionary emitter
164  emitter = builder.emitter[suffix]
165  builder.emitter[suffix] = SCons.Builder.ListEmitter([
166  emitter,
167  makeEmitCompilationDbEntry(command),
168  ])
169 
170  env['BUILDERS']['__COMPILATIONDB_Entry'] = SCons.Builder.Builder(
171  action=SCons.Action.Action(CompilationDbEntryAction, None), )
172 
173  env['BUILDERS']['__COMPILATIONDB_Database'] = SCons.Builder.Builder(
174  action=SCons.Action.Action(WriteCompilationDb, "$COMPILATIONDB_COMSTR"),
175  target_scanner=SCons.Scanner.Scanner(function=ScanCompilationDb, node_class=None))
176 
177  def CompilationDatabase(env, target):
178  result = env.__COMPILATIONDB_Database(target=target, source=[])
179 
180  env.AlwaysBuild(result)
181  env.NoCache(result)
182 
183  return result
184 
185  env.AddMethod(CompilationDatabase, 'CompilationDatabase')
186 
187 
188 def exists(env):
189  return True