@@ -21,49 +21,90 @@ def get_project_root():
21
21
project_root = dirpath .parent
22
22
return project_root
23
23
24
-
25
24
def execute_command (command , log_file_path = None ):
26
25
"""
27
- Executes a shell command.
26
+ Executes a shell command, streaming output to console, logging, and capturing errors .
28
27
29
28
Parameters:
30
29
- command: Command to be executed as a formatted string.
31
30
- log_file_path: Optional path to a log file to capture the command output.
32
31
33
- Note:
34
- - If no log_file_path is specified, the outputs will be printed in the terminal and predefined logfile .
35
- - If log_file_path is specified, the log will only be written to the specified file.
32
+ Returns:
33
+ - (stdout, stderr): A tuple containing the complete stdout and stderr of the command.
34
+
35
+ Raises:
36
+ - subprocess.CalledProcessError if the command exits with a non-zero status.
36
37
"""
37
38
logger .info (command .strip ())
38
39
40
+ # Open the log file if a path is specified
41
+ log_file = None
39
42
if log_file_path :
40
- with log_file_path .open ('a' ) as log_file :
41
- process = subprocess .run (
42
- command ,
43
- shell = True ,
44
- executable = '/bin/bash' ,
45
- check = True ,
46
- stdout = log_file ,
47
- stderr = subprocess .STDOUT ,
48
- text = True ,
49
- encoding = 'utf-8'
50
- )
51
- else :
52
- process = subprocess .run (
53
- command ,
54
- shell = True ,
55
- executable = '/bin/bash' ,
56
- check = True ,
57
- capture_output = True ,
58
- text = True ,
59
- encoding = 'utf-8'
60
- )
61
- if process .stdout :
62
- logger .info (process .stdout )
63
- if process .stderr :
64
- logger .error (process .stderr )
43
+ log_file = log_file_path .open ('a' , encoding = 'utf-8' )
44
+
45
+ # Start the process
46
+ with subprocess .Popen (command , shell = True , executable = '/bin/bash' ,
47
+ stdout = subprocess .PIPE , stderr = subprocess .PIPE ,
48
+ text = True , encoding = 'utf-8' ) as process :
49
+
50
+ # To collect the complete stdout and stderr
51
+ full_stdout = []
52
+ full_stderr = []
53
+
54
+ # Stream stdout and stderr in real-time
55
+ while True :
56
+ stdout_line = process .stdout .readline ()
57
+ stderr_line = process .stderr .readline ()
58
+
59
+ # Handle stdout
60
+ if stdout_line :
61
+ full_stdout .append (stdout_line )
62
+ if log_file :
63
+ log_file .write (stdout_line )
64
+ log_file .flush () # Ensure immediate write
65
+ logger .info (stdout_line .strip ())
66
+
67
+ # Handle stderr
68
+ if stderr_line :
69
+ full_stderr .append (stderr_line )
70
+ if log_file :
71
+ log_file .write (stderr_line )
72
+ log_file .flush ()
73
+ logger .error (stderr_line .strip ())
74
+
75
+ # If the process has finished and there are no more lines, break
76
+ if process .poll () is not None and not stdout_line and not stderr_line :
77
+ break
78
+
79
+ # Communicate to ensure we capture everything left in the buffers
80
+ remaining_stdout , remaining_stderr = process .communicate ()
81
+
82
+ # Handle remaining stdout
83
+ if remaining_stdout :
84
+ full_stdout .append (remaining_stdout )
85
+ if log_file :
86
+ log_file .write (remaining_stdout )
87
+ log_file .flush ()
88
+ logger .info (remaining_stdout .strip ())
89
+
90
+ # Handle remaining stderr
91
+ if remaining_stderr :
92
+ full_stderr .append (remaining_stderr )
93
+ if log_file :
94
+ log_file .write (remaining_stderr )
95
+ log_file .flush ()
96
+ logger .error (remaining_stderr .strip ())
97
+
98
+ # Close the log file if it was opened
99
+ if log_file :
100
+ log_file .close ()
101
+
102
+ # Combine all the lines into final stdout and stderr strings
103
+ stdout_str = '' .join (full_stdout )
104
+ stderr_str = '' .join (full_stderr )
105
+
106
+ return process
65
107
66
- return (process )
67
108
68
109
def command_prepare (command ):
69
110
return f'set -o pipefail; { textwrap .dedent (command )} '
0 commit comments