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
42-
43- from . cxx import CXXSuffixes
44- from .cc import CSuffixes
45- from . asm import ASSuffixes , ASPPSuffixes
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
4647
4748DEFAULT_DB_NAME = 'compile_commands.json'
4849
49- # TODO: Is there a better way to do this than this global? Right now this exists so that the
50- # emitter we add can record all of the things it emits, so that the scanner for the top level
51- # compilation database can access the complete list, and also so that the writer has easy
52- # access to write all of the files. But it seems clunky. How can the emitter and the scanner
53- # communicate more gracefully?
54- __COMPILATION_DB_ENTRIES = []
55-
56-
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-
11450
11551class CompDBTEMPFILE (TempFileMunge ):
11652 def __call__ (self , target , source , env , for_signature ):
11753 return self .cmd
11854
11955
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-
14956def write_compilation_db (target , source , env ) -> None :
150- entries = []
57+ DIRECTORY = env .Dir ("#" ).get_abspath ()
58+ OVERRIDES = {"TEMPFILE" : CompDBTEMPFILE }
59+ USE_ABSPATH = env ['COMPILATIONDB_USE_ABSPATH' ] in [True , 1 , 'True' , 'true' ]
60+ USE_PATH_FILTER = env .subst ('$COMPILATIONDB_PATH_FILTER' )
15161
152- use_abspath = env ['COMPILATIONDB_USE_ABSPATH' ] in [True , 1 , 'True' , 'true' ]
153- use_path_filter = env .subst ('$COMPILATIONDB_PATH_FILTER' )
154-
155- for s in __COMPILATION_DB_ENTRIES :
156- entry = s .read ()
157- source_file = entry ['file' ]
158- output_file = entry ['output' ]
62+ entries = []
63+ for db_target , db_source , db_env , db_action in env ._compilation_db_entries :
64+ # Parse command before filtering.
65+ command = db_action .strfunction (db_target , db_source , db_env , None , OVERRIDES )
15966
160- if not source_file .is_derived ():
161- source_file = source_file .srcnode ()
67+ if not db_source .is_derived ():
68+ db_source = db_source .srcnode ()
16269
163- if use_abspath :
164- source_file = source_file . abspath
165- output_file = output_file . abspath
70+ if USE_ABSPATH :
71+ file = db_source . get_abspath ()
72+ output = db_target . get_abspath ()
16673 else :
167- source_file = source_file . path
168- output_file = output_file . path
74+ file = db_source . get_path ()
75+ output = db_target . get_path ()
16976
170- if use_path_filter and not fnmatch .fnmatch (output_file , use_path_filter ):
77+ if USE_PATH_FILTER and not fnmatch .fnmatch (output , USE_PATH_FILTER ):
17178 continue
17279
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 = ("," , ": " )
80+ entries .append (
81+ {
82+ "command" : command ,
83+ "directory" : DIRECTORY ,
84+ "file" : file ,
85+ "output" : output ,
86+ }
18387 )
184- output_file .write ("\n " )
185-
18688
187- def scan_compilation_db (node , env , path ):
188- return __COMPILATION_DB_ENTRIES
89+ with open (target [0 ].get_path (), "w" , encoding = "utf-8" , newline = "\n " ) as output_file :
90+ json .dump (entries , output_file , sort_keys = True , indent = 4 )
91+ output_file .write ("\n " )
18992
19093
19194def compilation_db_emitter (target , source , env ):
192- """ fix up the source/targets """
95+ """Fix up the source/targets"""
19396
19497 # Someone called env.CompilationDatabase('my_targetname.json')
19598 if not target and len (source ) == 1 :
@@ -202,75 +105,68 @@ def compilation_db_emitter(target, source, env):
202105 if source :
203106 source = []
204107
108+ # TODO: Should eventually have a way to allow the entries themselves to
109+ # function as dependencies.
110+ env .AlwaysBuild (target )
111+ env .NoCache (target )
112+
205113 return target , source
206114
207115
208116def generate (env , ** kwargs ) -> None :
209- static_obj , shared_obj = SCons .Tool .createObjBuilders (env )
117+ def _generate_emitter (command ):
118+ # Construct new action to bypass `COMSTR`.
119+ action = Action (command )
210120
211- env ["COMPILATIONDB_COMSTR" ] = kwargs .get (
212- "COMPILATIONDB_COMSTR" , "Building compilation database $TARGET"
213- )
121+ def _compilation_db_entry_emitter (target , source , env ):
122+ env ._compilation_db_entries .append ((target [0 ], source [0 ], env , action ))
123+ return target , source
124+
125+ return _compilation_db_entry_emitter
126+
127+ GEN_CCCOM = _generate_emitter ("$CCCOM" )
128+ GEN_SHCCCOM = _generate_emitter ("$SHCCCOM" )
129+ GEN_CXXCOM = _generate_emitter ("$CXXCOM" )
130+ GEN_SHCXXCOM = _generate_emitter ("$SHCXXCOM" )
131+ GEN_ASCOM = _generate_emitter ("$ASCOM" )
132+ GEN_ASPPCOM = _generate_emitter ("$ASPPCOM" )
214133
215- components_by_suffix = itertools .chain (
134+ env ._compilation_db_entries = []
135+ static_obj , shared_obj = createObjBuilders (env )
136+
137+ for suffix , (builder , emitter ) in itertools .chain (
216138 itertools .product (
217- CSuffixes ,
218- [
219- (static_obj , SCons .Defaults .StaticObjectEmitter , "$CCCOM" ),
220- (shared_obj , SCons .Defaults .SharedObjectEmitter , "$SHCCCOM" ),
221- ],
139+ CSuffixes , [(static_obj , GEN_CCCOM ), (shared_obj , GEN_SHCCCOM )]
222140 ),
223141 itertools .product (
224- CXXSuffixes ,
225- [
226- (static_obj , SCons .Defaults .StaticObjectEmitter , "$CXXCOM" ),
227- (shared_obj , SCons .Defaults .SharedObjectEmitter , "$SHCXXCOM" ),
228- ],
142+ CXXSuffixes , [(static_obj , GEN_CXXCOM ), (shared_obj , GEN_SHCXXCOM )]
229143 ),
230144 itertools .product (
231- ASSuffixes ,
232- [
233- (static_obj , SCons .Defaults .StaticObjectEmitter , "$ASCOM" ),
234- (shared_obj , SCons .Defaults .SharedObjectEmitter , "$ASCOM" )
235- ],
145+ ASSuffixes , [(static_obj , GEN_ASCOM ), (shared_obj , GEN_ASCOM )]
236146 ),
237147 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
148+ ASPPSuffixes , [(static_obj , GEN_ASPPCOM ), (shared_obj , GEN_ASPPCOM )]
267149 ),
150+ ):
151+ emitter_old = builder .emitter .get (suffix )
152+ # Only setup emitters for Tools supported by the environment.
153+ if emitter_old :
154+ builder .emitter [suffix ] = ListEmitter (env .Flatten (emitter_old ) + [emitter ])
155+
156+ env ["BUILDERS" ]["CompilationDatabase" ] = Builder (
157+ action = Action (write_compilation_db , "$COMPILATIONDB_COMSTR" ),
268158 emitter = compilation_db_emitter ,
269- suffix = ' json' ,
159+ suffix = " json" ,
270160 )
271161
272- env ['COMPILATIONDB_USE_ABSPATH' ] = False
273- env ['COMPILATIONDB_PATH_FILTER' ] = ''
162+ if "COMPILATIONDB_USE_ABSPATH" not in env :
163+ env ["COMPILATIONDB_USE_ABSPATH" ] = False
164+ if "COMPILATIONDB_PATH_FILTER" not in env :
165+ env ["COMPILATIONDB_PATH_FILTER" ] = ""
166+ if "COMPILATIONDB_COMSTR" not in env :
167+ env ["COMPILATIONDB_COMSTR" ] = kwargs .get (
168+ "COMPILATIONDB_COMSTR" , "Building compilation database $TARGET"
169+ )
274170
275171
276172def exists (env ) -> bool :
0 commit comments