1+ from collections import defaultdict
12import logging
23import os
34import re
@@ -117,7 +118,7 @@ def filterFlags(items, fileCache):
117118 # continue
118119 if arg in { # will make sourcekit report errors
119120 "-use-frontend-parseable-output" ,
120- "-emit-localized-strings"
121+ "-emit-localized-strings" ,
121122 # "-frontend", "-c", "-pch-disable-validation", "-index-system-modules", "-enable-objc-interop",
122123 # '-whole-module-optimization',
123124 }:
@@ -164,36 +165,89 @@ def findSwiftModuleRoot(filename):
164165
165166 return (directory , flagFile , compileFile )
166167
168+
167169class CompileFileInfo :
168170 def __init__ (self , compileFile , store ):
169- self .info = {}
171+ self .file_info = {} # {file: command}
172+ self .dir_info = None # {dir: set[file key]}
173+ self .cmd_info = None # {cmd: set[file key]}
170174
171175 # load compileFile into info
172176 import json
177+
173178 with open (compileFile ) as f :
174179 m : List [dict ] = json .load (f )
175180 for i in m :
176181 command = i .get ("command" )
177182 if not command :
178183 continue
179184 if files := i .get ("files" ): # batch files, eg: swift module
180- self .info .update ((self .key (f ), command ) for f in files )
181- if fileLists := i .get ("fileLists" ): # file list store in a dedicated file
182- self .info .update (
185+ self .file_info .update ((self .key (f ), command ) for f in files )
186+ if fileLists := i .get (
187+ "fileLists"
188+ ): # file list store in a dedicated file
189+ self .file_info .update (
183190 (self .key (f ), command )
184- for l in fileLists if os .path .isfile (l )
191+ for l in fileLists
192+ if os .path .isfile (l )
185193 for f in getFileArgs (l , store .setdefault ("filelist" , {}))
186194 )
187195 if file := i .get ("file" ): # single file info
188- self .info [self .key (file )] = command
196+ self .file_info [self .key (file )] = command
189197
190198 def get (self , filename ):
191- if command := self .info .get (filename .lower ()):
199+ if command := self .file_info .get (filename .lower ()):
192200 return command .replace ("\\ =" , "=" )
193201
194202 def key (self , filename ):
195203 return os .path .realpath (filename ).lower ()
196204
205+ def groupby_dir (self ) -> dict [str , set [str ]]:
206+ if self .dir_info is None : # lazy index dir and cmd
207+ self .dir_info = defaultdict (set )
208+ self .cmd_info = defaultdict (set )
209+ for f , cmd in self .file_info .items ():
210+ self .dir_info [os .path .dirname (f )].add (f )
211+ self .cmd_info [cmd ].add (f )
212+
213+ return self .dir_info
214+
215+ # hack new file into current compile file
216+ def new_file (self , filename ):
217+ # Currently only processing swift files
218+ if not filename .endswith (".swift" ):
219+ return
220+
221+ filename = os .path .realpath (filename )
222+ filename_key = filename .lower ()
223+ if filename_key in self .file_info :
224+ return # already handled
225+
226+ dir = os .path .dirname (filename_key )
227+ samefile = next (
228+ (v for v in self .groupby_dir ().get (dir , ()) if v .endswith (".swift" )), None
229+ )
230+ if not samefile :
231+ return
232+
233+ command = self .file_info [samefile ]
234+ cmd_match = next (cmd_split_pattern .finditer (command ), None )
235+ if not cmd_match :
236+ return
237+ assert self .cmd_info
238+ module_files = self .cmd_info .pop (command )
239+ index = cmd_match .end ()
240+ from shlex import quote
241+
242+ command = "" .join ((command [:index ], " " , quote (filename ), command [index :]))
243+
244+ # update command info
245+ self .groupby_dir ()[dir ].add (filename_key )
246+ module_files .add (filename_key )
247+ self .cmd_info [command ] = module_files
248+ for v in module_files :
249+ self .file_info [v ] = command
250+
197251
198252def commandForFile (filename , compileFile , store : Dict ):
199253 """
@@ -202,9 +256,14 @@ def commandForFile(filename, compileFile, store: Dict):
202256 compile_store = store .setdefault ("compile" , {})
203257 info : CompileFileInfo = compile_store .get (compileFile )
204258 if info is None : # load {filename.lower: command} dict
205- info = CompileFileInfo (compileFile , store ) # cache first to avoid re enter when error
259+ # cache first to avoid re enter when error
260+ info = CompileFileInfo (compileFile , store )
206261 compile_store [compileFile ] = info
207262
263+ # if has additional new_file, generate command for it
264+ for file in store .get ("additional_files" ) or ():
265+ info .new_file (file )
266+
208267 # xcode 12 escape =, but not recognized...
209268 return info .get (filename )
210269
@@ -233,6 +292,7 @@ def GetFlags(filename: str, compileFile=None, **kwargs):
233292 return InferFlagsForSwift (filename , compileFile , store )
234293 return {"flags" : [], "do_cache" : False }
235294
295+
236296# TODO: c family infer flags #
237297def InferFlagsForSwift (filename , compileFile , store ):
238298 """try infer flags by convention and workspace files"""
0 commit comments