3333``compile_commands.json``, the name that most clang tools search for by default.
3434"""
3535
36- import json
37- import itertools
3836import fnmatch
39- import SCons
37+ import itertools
38+ import json
4039
40+ from SCons .Action import Action
41+ from SCons .Builder import Builder , ListEmitter
4142from SCons .Platform import TempFileMunge
43+ from SCons .Tool import createObjBuilders
44+ from SCons .Tool .asm import ASPPSuffixes , ASSuffixes
45+ from SCons .Tool .cc import CSuffixes
46+ from SCons .Tool .cxx import CXXSuffixes
4247
43- from .cxx import CXXSuffixes
44- from .cc import CSuffixes
45- from .asm import ASSuffixes , ASPPSuffixes
46-
47- DEFAULT_DB_NAME = 'compile_commands.json'
48+ DEFAULT_DB_NAME = "compile_commands.json"
4849
4950# TODO: Is there a better way to do this than this global? Right now this exists so that the
5051# emitter we add can record all of the things it emits, so that the scanner for the top level
5455__COMPILATION_DB_ENTRIES = []
5556
5657
57- # We make no effort to avoid rebuilding the entries. Someday, perhaps we could and even
58- # integrate with the cache, but there doesn't seem to be much call for it.
59- class __CompilationDbNode (SCons .Node .Python .Value ):
60- def __init__ (self , value ) -> None :
61- SCons .Node .Python .Value .__init__ (self , value )
62- self .Decider (changed_since_last_build_node )
63-
64-
65- def changed_since_last_build_node (child , target , prev_ni , node ) -> bool :
66- """ Dummy decider to force always building"""
67- return True
68-
69-
70- def make_emit_compilation_DB_entry (comstr ):
71- """
72- Effectively this creates a lambda function to capture:
73- * command line
74- * source
75- * target
76- :param comstr: unevaluated command line
77- :return: an emitter which has captured the above
78- """
79- user_action = SCons .Action .Action (comstr )
80-
81- def emit_compilation_db_entry (target , source , env ):
82- """
83- This emitter will be added to each c/c++ object build to capture the info needed
84- for clang tools
85- :param target: target node(s)
86- :param source: source node(s)
87- :param env: Environment for use building this node
88- :return: target(s), source(s)
89- """
90-
91- dbtarget = __CompilationDbNode (source )
92-
93- entry = env .__COMPILATIONDB_Entry (
94- target = dbtarget ,
95- source = [],
96- __COMPILATIONDB_UOUTPUT = target ,
97- __COMPILATIONDB_USOURCE = source ,
98- __COMPILATIONDB_UACTION = user_action ,
99- __COMPILATIONDB_ENV = env ,
100- )
101-
102- # TODO: Technically, these next two lines should not be required: it should be fine to
103- # cache the entries. However, they don't seem to update properly. Since they are quick
104- # to re-generate disable caching and sidestep this problem.
105- env .AlwaysBuild (entry )
106- env .NoCache (entry )
107-
108- __COMPILATION_DB_ENTRIES .append (dbtarget )
109-
110- return target , source
111-
112- return emit_compilation_db_entry
113-
114-
11558class CompDBTEMPFILE (TempFileMunge ):
11659 def __call__ (self , target , source , env , for_signature ):
11760 return self .cmd
11861
11962
120- def compilation_db_entry_action (target , source , env , ** kw ) -> None :
121- """
122- Create a dictionary with evaluated command line, target, source
123- and store that info as an attribute on the target
124- (Which has been stored in __COMPILATION_DB_ENTRIES array
125- :param target: target node(s)
126- :param source: source node(s)
127- :param env: Environment for use building this node
128- :param kw:
129- :return: None
130- """
131-
132- command = env ["__COMPILATIONDB_UACTION" ].strfunction (
133- target = env ["__COMPILATIONDB_UOUTPUT" ],
134- source = env ["__COMPILATIONDB_USOURCE" ],
135- env = env ["__COMPILATIONDB_ENV" ],
136- overrides = {'TEMPFILE' : CompDBTEMPFILE }
137- )
138-
139- entry = {
140- "directory" : env .Dir ("#" ).abspath ,
141- "command" : command ,
142- "file" : env ["__COMPILATIONDB_USOURCE" ][0 ],
143- "output" : env ['__COMPILATIONDB_UOUTPUT' ][0 ]
144- }
145-
146- target [0 ].write (entry )
147-
148-
14963def write_compilation_db (target , source , env ) -> None :
150- entries = []
151-
152- use_abspath = env [' COMPILATIONDB_USE_ABSPATH' ] in [True , 1 , ' True' , ' true' ]
153- use_path_filter = env .subst (' $COMPILATIONDB_PATH_FILTER' )
64+ directory = env . Dir ( "#" ). get_abspath ()
65+ overrides = { "TEMPFILE" : CompDBTEMPFILE }
66+ use_abspath = env [" COMPILATIONDB_USE_ABSPATH" ] in [True , 1 , " True" , " true" ]
67+ use_path_filter = env .subst (" $COMPILATIONDB_PATH_FILTER" )
15468
155- for s in __COMPILATION_DB_ENTRIES :
156- entry = s . read ()
157- source_file = entry [ 'file' ]
158- output_file = entry [ 'output' ]
69+ entries = []
70+ for db_target , db_source , db_env , db_action in __COMPILATION_DB_ENTRIES :
71+ # Parse command before filtering.
72+ command = db_action . strfunction ( db_target , db_source , db_env , None , overrides )
15973
160- if not source_file .is_derived ():
161- source_file = source_file .srcnode ()
74+ if not db_source .is_derived ():
75+ db_source = db_source .srcnode ()
16276
16377 if use_abspath :
164- source_file = source_file . abspath
165- output_file = output_file . abspath
78+ file = db_source . get_abspath ()
79+ output = db_target . get_abspath ()
16680 else :
167- source_file = source_file . path
168- output_file = output_file . path
81+ file = db_source . get_path ()
82+ output = db_target . get_path ()
16983
170- if use_path_filter and not fnmatch .fnmatch (output_file , use_path_filter ):
84+ if use_path_filter and not fnmatch .fnmatch (output , use_path_filter ):
17185 continue
17286
173- path_entry = {'directory' : entry ['directory' ],
174- 'command' : entry ['command' ],
175- 'file' : source_file ,
176- 'output' : output_file }
177-
178- entries .append (path_entry )
179-
180- with open (target [0 ].path , "w" ) as output_file :
181- json .dump (
182- entries , output_file , sort_keys = True , indent = 4 , separators = ("," , ": " )
87+ entries .append (
88+ {
89+ "command" : command ,
90+ "directory" : directory ,
91+ "file" : file ,
92+ "output" : output ,
93+ }
18394 )
184- output_file .write ("\n " )
185-
18695
187- def scan_compilation_db (node , env , path ):
188- return __COMPILATION_DB_ENTRIES
96+ with open (target [0 ].get_path (), "w" , encoding = "utf-8" , newline = "\n " ) as output_file :
97+ json .dump (entries , output_file , sort_keys = True , indent = 4 )
98+ output_file .write ("\n " )
18999
190100
191101def compilation_db_emitter (target , source , env ):
192- """ fix up the source/targets """
102+ """Fix up the source/targets"""
193103
194104 # Someone called env.CompilationDatabase('my_targetname.json')
195105 if not target and len (source ) == 1 :
@@ -202,75 +112,65 @@ def compilation_db_emitter(target, source, env):
202112 if source :
203113 source = []
204114
115+ # TODO: Should eventually have a way to allow the entries themselves to
116+ # function as dependencies.
117+ env .AlwaysBuild (target )
118+ env .NoCache (target )
119+
205120 return target , source
206121
207122
208123def generate (env , ** kwargs ) -> None :
209- static_obj , shared_obj = SCons .Tool .createObjBuilders (env )
124+ def _generate_emitter (command ):
125+ # Construct new action to bypass `COMSTR`.
126+ action = Action (command )
210127
211- env ["COMPILATIONDB_COMSTR" ] = kwargs .get (
212- "COMPILATIONDB_COMSTR" , "Building compilation database $TARGET"
213- )
128+ def _compilation_db_entry_emitter (target , source , env ):
129+ __COMPILATION_DB_ENTRIES .append ((target [0 ], source [0 ], env , action ))
130+ return target , source
131+
132+ return _compilation_db_entry_emitter
133+
134+ cc_emitter = _generate_emitter ("$CCCOM" )
135+ shcc_emitter = _generate_emitter ("$SHCCCOM" )
136+ cxx_emitter = _generate_emitter ("$CXXCOM" )
137+ shcxx_emitter = _generate_emitter ("$SHCXXCOM" )
138+ as_emitter = _generate_emitter ("$ASCOM" )
139+ aspp_emitter = _generate_emitter ("$ASPPCOM" )
214140
215- components_by_suffix = itertools .chain (
141+ static_obj , shared_obj = createObjBuilders (env )
142+
143+ for suffix , (builder , emitter ) in itertools .chain (
216144 itertools .product (
217- CSuffixes ,
218- [
219- (static_obj , SCons .Defaults .StaticObjectEmitter , "$CCCOM" ),
220- (shared_obj , SCons .Defaults .SharedObjectEmitter , "$SHCCCOM" ),
221- ],
145+ CSuffixes , [(static_obj , cc_emitter ), (shared_obj , shcc_emitter )]
222146 ),
223147 itertools .product (
224- CXXSuffixes ,
225- [
226- (static_obj , SCons .Defaults .StaticObjectEmitter , "$CXXCOM" ),
227- (shared_obj , SCons .Defaults .SharedObjectEmitter , "$SHCXXCOM" ),
228- ],
148+ CXXSuffixes , [(static_obj , cxx_emitter ), (shared_obj , shcxx_emitter )]
229149 ),
230150 itertools .product (
231- ASSuffixes ,
232- [
233- (static_obj , SCons .Defaults .StaticObjectEmitter , "$ASCOM" ),
234- (shared_obj , SCons .Defaults .SharedObjectEmitter , "$ASCOM" )
235- ],
151+ ASSuffixes , [(static_obj , as_emitter ), (shared_obj , as_emitter )]
236152 ),
237153 itertools .product (
238- ASPPSuffixes ,
239- [
240- (static_obj , SCons .Defaults .StaticObjectEmitter , "$ASPPCOM" ),
241- (shared_obj , SCons .Defaults .SharedObjectEmitter , "$ASPPCOM" )
242- ],
243- ),
244- )
245-
246- for entry in components_by_suffix :
247- suffix = entry [0 ]
248- builder , base_emitter , command = entry [1 ]
249-
250- # Assumes a dictionary emitter
251- emitter = builder .emitter .get (suffix , False )
252- if emitter :
253- # We may not have tools installed which initialize all or any of
254- # cxx, cc, or assembly. If not skip resetting the respective emitter.
255- builder .emitter [suffix ] = SCons .Builder .ListEmitter (
256- [emitter , make_emit_compilation_DB_entry (command ), ]
257- )
258-
259- env ["BUILDERS" ]["__COMPILATIONDB_Entry" ] = SCons .Builder .Builder (
260- action = SCons .Action .Action (compilation_db_entry_action , None ),
261- )
262-
263- env ["BUILDERS" ]["CompilationDatabase" ] = SCons .Builder .Builder (
264- action = SCons .Action .Action (write_compilation_db , "$COMPILATIONDB_COMSTR" ),
265- target_scanner = SCons .Scanner .Scanner (
266- function = scan_compilation_db , node_class = None
154+ ASPPSuffixes , [(static_obj , aspp_emitter ), (shared_obj , aspp_emitter )]
267155 ),
156+ ):
157+ emitter_old = builder .emitter .get (suffix )
158+ # Only setup emitters for Tools supported by the environment.
159+ if emitter_old :
160+ builder .emitter [suffix ] = ListEmitter (env .Flatten (emitter_old ) + [emitter ])
161+
162+ env ["BUILDERS" ]["CompilationDatabase" ] = Builder (
163+ action = Action (write_compilation_db , "$COMPILATIONDB_COMSTR" ),
268164 emitter = compilation_db_emitter ,
269- suffix = ' json' ,
165+ suffix = " json" ,
270166 )
271167
272- env ['COMPILATIONDB_USE_ABSPATH' ] = False
273- env ['COMPILATIONDB_PATH_FILTER' ] = ''
168+ env ["COMPILATIONDB_USE_ABSPATH" ] = env .get ("COMPILATIONDB_USE_ABSPATH" , False )
169+ env ["COMPILATIONDB_PATH_FILTER" ] = env .get ("COMPILATIONDB_PATH_FILTER" , "" )
170+ env ["COMPILATIONDB_COMSTR" ] = env .get (
171+ "COMPILATIONDB_COMSTR" ,
172+ kwargs .get ("COMPILATIONDB_COMSTR" , "Building compilation database $TARGET" ),
173+ )
274174
275175
276176def exists (env ) -> bool :
0 commit comments