7676]
7777
7878
79- RunShellCmdResult = namedtuple ('RunShellCmdResult' , ('cmd' , 'exit_code' , 'output' , 'stderr' , 'work_dir' ))
79+ RunShellCmdResult = namedtuple ('RunShellCmdResult' , ('cmd' , 'exit_code' , 'output' , 'stderr' , 'work_dir' ,
80+ 'out_file' , 'err_file' ))
8081
8182
8283class RunShellCmdError (BaseException ):
8384
84- def __init__ (self , cmd , exit_code , work_dir , output , stderr , caller_info , * args , ** kwargs ):
85+ def __init__ (self , cmd_result , caller_info , * args , ** kwargs ):
8586 """Constructor for RunShellCmdError."""
86- self .cmd = cmd
87- self .cmd_name = cmd .split (' ' )[0 ]
88- self .exit_code = exit_code
89- self .work_dir = work_dir
90- self .output = output
91- self .stderr = stderr
87+ self .cmd_result = cmd_result
88+ self .cmd_name = cmd_result .cmd .split (' ' )[0 ]
9289 self .caller_info = caller_info
9390
9491 msg = f"Shell command '{ self .cmd_name } ' failed!"
@@ -105,25 +102,17 @@ def pad_4_spaces(msg):
105102 error_info = [
106103 '' ,
107104 "ERROR: Shell command failed!" ,
108- pad_4_spaces (f"full command -> { self .cmd } " ),
109- pad_4_spaces (f"exit code -> { self .exit_code } " ),
110- pad_4_spaces (f"working directory -> { self .work_dir } " ),
105+ pad_4_spaces (f"full command -> { self .cmd_result . cmd } " ),
106+ pad_4_spaces (f"exit code -> { self .cmd_result . exit_code } " ),
107+ pad_4_spaces (f"working directory -> { self .cmd_result . work_dir } " ),
111108 ]
112109
113- tmpdir = tempfile .mkdtemp (prefix = 'shell-cmd-error-' )
114- output_fp = os .path .join (tmpdir , f"{ self .cmd_name } .out" )
115- with open (output_fp , 'w' ) as fp :
116- fp .write (self .output or '' )
117-
118- if self .stderr is None :
119- error_info .append (pad_4_spaces (f"output (stdout + stderr) -> { output_fp } " ))
110+ if self .cmd_result .stderr is None :
111+ error_info .append (pad_4_spaces (f"output (stdout + stderr) -> { self .cmd_result .out_file } " ))
120112 else :
121- stderr_fp = os .path .join (tmpdir , f"{ self .cmd_name } .err" )
122- with open (stderr_fp , 'w' ) as fp :
123- fp .write (self .stderr )
124113 error_info .extend ([
125- pad_4_spaces (f"output (stdout) -> { output_fp } " ),
126- pad_4_spaces (f"error/warnings (stderr) -> { stderr_fp } " ),
114+ pad_4_spaces (f"output (stdout) -> { self . cmd_result . out_file } " ),
115+ pad_4_spaces (f"error/warnings (stderr) -> { self . cmd_result . err_file } " ),
127116 ])
128117
129118 caller_file_name , caller_line_nr , caller_function_name = self .caller_info
@@ -136,9 +125,9 @@ def pad_4_spaces(msg):
136125 sys .stderr .write ('\n ' .join (error_info ) + '\n ' )
137126
138127
139- def raise_run_shell_cmd_error (cmd , exit_code , work_dir , output , stderr ):
128+ def raise_run_shell_cmd_error (cmd_res ):
140129 """
141- Raise RunShellCmdError for failing shell command, after collecting additional caller info
130+ Raise RunShellCmdError for failed shell command, after collecting additional caller info
142131 """
143132
144133 # figure out where failing command was run
@@ -150,7 +139,7 @@ def raise_run_shell_cmd_error(cmd, exit_code, work_dir, output, stderr):
150139 frameinfo = inspect .getouterframes (inspect .currentframe ())[3 ]
151140 caller_info = (frameinfo .filename , frameinfo .lineno , frameinfo .function )
152141
153- raise RunShellCmdError (cmd , exit_code , work_dir , output , stderr , caller_info )
142+ raise RunShellCmdError (cmd_res , caller_info )
154143
155144
156145def run_cmd_cache (func ):
@@ -234,16 +223,18 @@ def to_cmd_str(cmd):
234223 if work_dir is None :
235224 work_dir = os .getcwd ()
236225
237- # temporary output file for command output, if requested
238- if output_file or not hidden :
239- # collect output of running command in temporary log file, if desired
240- fd , cmd_out_fp = tempfile .mkstemp (suffix = '.log' , prefix = 'easybuild-run-' )
241- os .close (fd )
242- _log .info (f'run_cmd: Output of "{ cmd } " will be logged to { cmd_out_fp } ' )
243- else :
244- cmd_out_fp = None
245-
246226 cmd_str = to_cmd_str (cmd )
227+ cmd_name = cmd_str .split (' ' )[0 ]
228+
229+ # temporary output file(s) for command output
230+ tmpdir = tempfile .mkdtemp (prefix = 'run-shell-cmd-' )
231+ cmd_out_fp = os .path .join (tmpdir , f'{ cmd_name } .out' )
232+ _log .info (f'run_cmd: Output of "{ cmd_str } " will be logged to { cmd_out_fp } ' )
233+ if split_stderr :
234+ cmd_err_fp = os .path .join (tmpdir , f'{ cmd_name } .err' )
235+ _log .info (f'run_cmd: Errors and warnings of "{ cmd_str } " will be logged to { cmd_err_fp } ' )
236+ else :
237+ cmd_err_fp = None
247238
248239 # early exit in 'dry run' mode, after printing the command that would be run (unless 'hidden' is enabled)
249240 if not in_dry_run and build_option ('extended_dry_run' ):
@@ -253,11 +244,12 @@ def to_cmd_str(cmd):
253244 msg += f" (in { work_dir } )"
254245 dry_run_msg (msg , silent = silent )
255246
256- return RunShellCmdResult (cmd = cmd_str , exit_code = 0 , output = '' , stderr = None , work_dir = work_dir )
247+ return RunShellCmdResult (cmd = cmd_str , exit_code = 0 , output = '' , stderr = None , work_dir = work_dir ,
248+ out_file = cmd_out_fp , err_file = cmd_err_fp )
257249
258250 start_time = datetime .now ()
259251 if not hidden :
260- cmd_trace_msg (cmd_str , start_time , work_dir , stdin , cmd_out_fp )
252+ cmd_trace_msg (cmd_str , start_time , work_dir , stdin , cmd_out_fp , cmd_err_fp )
261253
262254 if stdin :
263255 # 'input' value fed to subprocess.run must be a byte sequence
@@ -286,7 +278,18 @@ def to_cmd_str(cmd):
286278 output = proc .stdout .decode ('utf-8' , 'ignore' )
287279 stderr = proc .stderr .decode ('utf-8' , 'ignore' ) if split_stderr else None
288280
289- res = RunShellCmdResult (cmd = cmd_str , exit_code = proc .returncode , output = output , stderr = stderr , work_dir = work_dir )
281+ # store command output to temporary file(s)
282+ try :
283+ with open (cmd_out_fp , 'w' ) as fp :
284+ fp .write (output )
285+ if split_stderr :
286+ with open (cmd_err_fp , 'w' ) as fp :
287+ fp .write (stderr )
288+ except IOError as err :
289+ raise EasyBuildError (f"Failed to dump command output to temporary file: { err } " )
290+
291+ res = RunShellCmdResult (cmd = cmd_str , exit_code = proc .returncode , output = output , stderr = stderr , work_dir = work_dir ,
292+ out_file = cmd_out_fp , err_file = cmd_err_fp )
290293
291294 # always log command output
292295 cmd_name = cmd_str .split (' ' )[0 ]
@@ -301,7 +304,7 @@ def to_cmd_str(cmd):
301304 else :
302305 _log .warning (f"Shell command FAILED (exit code { res .exit_code } , see output above): { cmd_str } " )
303306 if fail_on_error :
304- raise_run_shell_cmd_error (res . cmd , res . exit_code , res . work_dir , output = res . output , stderr = res . stderr )
307+ raise_run_shell_cmd_error (res )
305308
306309 if with_hooks :
307310 run_hook_kwargs = {
@@ -319,15 +322,16 @@ def to_cmd_str(cmd):
319322 return res
320323
321324
322- def cmd_trace_msg (cmd , start_time , work_dir , stdin , cmd_out_fp ):
325+ def cmd_trace_msg (cmd , start_time , work_dir , stdin , cmd_out_fp , cmd_err_fp ):
323326 """
324327 Helper function to construct and print trace message for command being run
325328
326329 :param cmd: command being run
327330 :param start_time: datetime object indicating when command was started
328331 :param work_dir: path of working directory in which command is run
329332 :param stdin: stdin input value for command
330- :param cmd_out_fp: path to output log file for command
333+ :param cmd_out_fp: path to output file for command
334+ :param cmd_err_fp: path to errors/warnings output file for command
331335 """
332336 start_time = start_time .strftime ('%Y-%m-%d %H:%M:%S' )
333337
@@ -338,8 +342,9 @@ def cmd_trace_msg(cmd, start_time, work_dir, stdin, cmd_out_fp):
338342 ]
339343 if stdin :
340344 lines .append (f"\t [input: { stdin } ]" )
341- if cmd_out_fp :
342- lines .append (f"\t [output logged in { cmd_out_fp } ]" )
345+ lines .append (f"\t [output saved to { cmd_out_fp } ]" )
346+ if cmd_err_fp :
347+ lines .append (f"\t [errors/warnings saved to { cmd_err_fp } ]" )
343348
344349 lines .append ('\t ' + cmd )
345350
0 commit comments