88import re
99import tempfile
1010import time
11+ from typing import Optional
1112
1213logger = logging .getLogger (__name__ )
1314
@@ -109,7 +110,14 @@ def _create_semaphore():
109110max_track = 0
110111
111112
112- async def run_command (command , working_directory , description = None , check_hash = [], extradeps = []):
113+ async def run_command (
114+ command ,
115+ working_directory ,
116+ description = None ,
117+ check_hash = [],
118+ extradeps = [],
119+ responsefile : Optional [pathlib .Path ] = None ,
120+ ):
113121 """
114122 Runs a command asynchronously. The command should ideally be a list of strings
115123 and pathlib.Path objects. If all of the paths haven't been modified since the last
@@ -122,7 +130,11 @@ async def run_command(command, working_directory, description=None, check_hash=[
122130
123131 Paths in check_hash are hashed before and after the command. If the hash is
124132 the same, then the old mtimes are reset. This is helpful if a command may produce
125- the same result and you don't want the rest of the build impacted.
133+ the same result and you don't want the rest of the build impacted
134+
135+ responsefile is used to store the command line arguments if they are too long for the OS.
136+ The arguments will be replaced with @<responsefile> and tried again.
137+ If None, commands that are too long will fail.
126138 """
127139 paths = []
128140 if isinstance (command , list ):
@@ -133,9 +145,16 @@ async def run_command(command, working_directory, description=None, check_hash=[
133145 # if isinstance(part, list):
134146
135147 command [i ] = str (part )
136- command = " " .join (command )
148+ command_string = " " .join (command )
137149
138- command_hash = hashlib .sha3_256 (command .encode ("utf-8" ))
150+ # When on windows, use a responsefile if the command string is >= 8192
151+ if os .name == "nt" and len (command_string ) >= 8192 :
152+ responsefile .write_text ("\n " .join (command [1 :]))
153+ command_string = f"{ command [0 ]} @{ responsefile .name } "
154+ else :
155+ command_string = command
156+
157+ command_hash = hashlib .sha3_256 (command_string .encode ("utf-8" ))
139158 command_hash .update (str (working_directory ).encode ("utf-8" ))
140159 command_hash = command_hash .hexdigest ()
141160
@@ -187,21 +206,14 @@ async def run_command(command, working_directory, description=None, check_hash=[
187206
188207 cancellation = None
189208 async with shared_semaphore :
190- for i , part in enumerate (command .split ()):
191- if isinstance (part , str ) and part .startswith ("@" ):
192- print (part )
193- with open (part [1 :], "r" ) as f :
194- print ("Contents of file:" , part [1 :])
195- content = f .read ()
196- print (content )
197209 global max_track
198210 if not tracks :
199211 max_track += 1
200212 tracks .append (max_track )
201213 track = tracks .pop ()
202214 start_time = time .perf_counter_ns () // 1000
203215 process = await asyncio .create_subprocess_shell (
204- command ,
216+ command_string ,
205217 stdout = asyncio .subprocess .PIPE ,
206218 stderr = asyncio .subprocess .PIPE ,
207219 cwd = working_directory ,
@@ -247,22 +259,23 @@ async def run_command(command, working_directory, description=None, check_hash=[
247259 raise cancellation
248260 if description :
249261 logger .info (f"{ description } ({ run_reason } )" )
250- logger .debug (command )
262+ logger .debug (command_string )
251263 else :
252- logger .info (f"{ command } ({ run_reason } )" )
264+ logger .info (f"{ command_string } ({ run_reason } )" )
253265 if old_newest_file == newest_file :
254266 logger .error ("No files were modified by the command." )
255267 raise RuntimeError ()
256268 else :
257269 if command_hash in LAST_BUILD_TIMES :
258270 del LAST_BUILD_TIMES [command_hash ]
271+ logger .error (command_string )
272+ logger .error (f"Return code: { process .returncode } " )
259273 if stdout :
260274 logger .info (stdout .decode ("utf-8" ).strip ())
261275 if stderr :
262276 logger .warning (stderr .decode ("utf-8" ).strip ())
263277 if not stdout and not stderr :
264278 logger .warning ("No output" )
265- logger .error (command )
266279 if cancellation :
267280 raise cancellation
268281 raise RuntimeError ()
@@ -332,21 +345,6 @@ def __init__(self, srcdir: pathlib.Path, builddir: pathlib.Path, cmake_args):
332345 self .ar = cmake_args ["AR" ]
333346 self .cflags = cmake_args .get ("CFLAGS" , "" )
334347
335- self ._cflags_tempfile = tempfile .NamedTemporaryFile ()
336-
337- # Windows paths have / as separator but gcc wants them as escaped backslashes.
338- if os .name == "nt" :
339- escaped_cflags = self .cflags .replace ("/" , "\\ \\ " )
340- escaped_cflags = escaped_cflags .replace (" " , "\n " )
341- else :
342- escaped_cflags = self .cflags
343- print ("writing to:" , self ._cflags_tempfile .name )
344- print (escaped_cflags )
345- self ._cflags_tempfile .write (escaped_cflags .encode ())
346- self ._cflags_tempfile .flush ()
347- self .cflags_file = "@" + self ._cflags_tempfile .name
348- print ("cflags_file:" , self .cflags_file )
349-
350348 self .srcdir = srcdir
351349 self .builddir = builddir
352350
@@ -355,26 +353,24 @@ async def preprocess(
355353 ):
356354 output_file .parent .mkdir (parents = True , exist_ok = True )
357355 depfile = output_file .parent / (output_file .name + ".d" )
358- if depfile . exists ():
359- pass
356+ responsefile = output_file . parent / ( output_file . name + ".rsp" )
357+
360358 await run_command (
361359 [
362360 self .c_compiler ,
363361 "-E" ,
364362 "-MMD" ,
365- "-MF" ,
366- depfile ,
367- "-v" ,
363+ f"-MF { depfile } " ,
368364 "-c" ,
369365 source_file ,
370- self .cflags_file ,
366+ self .cflags ,
371367 * flags ,
372- "-o" ,
373- output_file ,
368+ f"-o { output_file } " ,
374369 ],
375370 description = f"Preprocess { source_file .relative_to (self .srcdir )} -> { output_file .relative_to (self .builddir )} " ,
376371 working_directory = self .srcdir ,
377372 check_hash = [output_file ],
373+ responsefile = responsefile ,
378374 )
379375
380376 async def compile (
@@ -386,6 +382,7 @@ async def compile(
386382 output_file = self .builddir / output_file
387383 output_file .parent .mkdir (parents = True , exist_ok = True )
388384 depfile = output_file .with_suffix (".d" )
385+ responsefile = output_file .with_suffix (".rsp" )
389386 extradeps = []
390387 if depfile .exists ():
391388 depfile_contents = depfile .read_text ().split ()
@@ -396,10 +393,11 @@ async def compile(
396393 extradeps .append (pathlib .Path (dep ))
397394 else :
398395 extradeps .append (self .srcdir / dep )
396+
399397 await run_command (
400398 [
401399 self .c_compiler ,
402- self .cflags_file ,
400+ self .cflags ,
403401 "-MMD" ,
404402 "-c" ,
405403 source_file ,
@@ -410,6 +408,7 @@ async def compile(
410408 description = f"Compile { source_file .relative_to (self .srcdir )} -> { output_file .relative_to (self .builddir )} " ,
411409 working_directory = self .srcdir ,
412410 extradeps = extradeps ,
411+ responsefile = responsefile ,
413412 )
414413
415414 async def archive (self , objects : list [pathlib .Path ], output_file : pathlib .Path ):
0 commit comments