1515import asyncio
1616import subprocess
1717import sys
18- from typing import (
19- List ,
20- Optional ,
21- Tuple ,
22- Union ,
23- IO ,
24- Any ,
25- cast ,
26- NamedTuple ,
27- )
18+ from typing import List , Optional , Tuple , Union , IO , Any , cast , NamedTuple
2819
2920from collections .abc import AsyncIterable
3021
31- CommandOutput = NamedTuple ("CommandOutput" , [
32- ('out' , Optional [str ]),
33- ('err' , Optional [str ]),
34- ('exit_code' , int ),
35- ])
22+ CommandOutput = NamedTuple (
23+ "CommandOutput" , [('out' , Optional [str ]), ('err' , Optional [str ]), ('exit_code' , int )]
24+ )
3625
3726BOLD = 1
3827DIM = 2
@@ -52,11 +41,7 @@ def highlight(text: str, color_code: int, bold: bool = False) -> str:
5241 Returns:
5342 The highlighted string.
5443 """
55- return '{}\033 [{}m{}\033 [0m' .format (
56- '\033 [1m' if bold else '' ,
57- color_code ,
58- text ,
59- )
44+ return '{}\033 [{}m{}\033 [0m' .format ('\033 [1m' if bold else '' , color_code , text )
6045
6146
6247class TeeCapture :
@@ -70,9 +55,9 @@ def __init__(self, out_pipe: Optional[IO[str]] = None) -> None:
7055 self .out_pipe = out_pipe
7156
7257
73- async def _async_forward (async_chunks : AsyncIterable ,
74- out : Optional [Union [TeeCapture , IO [str ]]]
75- ) -> Optional [str ]:
58+ async def _async_forward (
59+ async_chunks : AsyncIterable , out : Optional [Union [TeeCapture , IO [str ]]]
60+ ) -> Optional [str ]:
7661 """Prints/captures output from the given asynchronous iterable.
7762
7863 Args:
@@ -99,9 +84,9 @@ async def _async_forward(async_chunks: AsyncIterable,
9984
10085
10186async def _async_wait_for_process (
102- future_process : Any ,
103- out : Optional [Union [TeeCapture , IO [str ]]] = sys .stdout ,
104- err : Optional [Union [TeeCapture , IO [str ]]] = sys .stderr
87+ future_process : Any ,
88+ out : Optional [Union [TeeCapture , IO [str ]]] = sys .stdout ,
89+ err : Optional [Union [TeeCapture , IO [str ]]] = sys .stderr ,
10590) -> CommandOutput :
10691 """Awaits the creation and completion of an asynchronous process.
10792
@@ -122,8 +107,7 @@ async def _async_wait_for_process(
122107 return CommandOutput (output , err_output , process .returncode )
123108
124109
125- def abbreviate_command_arguments_after_switches (cmd : Tuple [str , ...]
126- ) -> Tuple [str , ...]:
110+ def abbreviate_command_arguments_after_switches (cmd : Tuple [str , ...]) -> Tuple [str , ...]:
127111 result = [cmd [0 ]]
128112 for i in range (1 , len (cmd )):
129113 if not cmd [i ].startswith ('-' ):
@@ -133,13 +117,15 @@ def abbreviate_command_arguments_after_switches(cmd: Tuple[str, ...]
133117 return tuple (result )
134118
135119
136- def run_cmd (* cmd : Optional [str ],
137- out : Optional [Union [TeeCapture , IO [str ]]] = sys .stdout ,
138- err : Optional [Union [TeeCapture , IO [str ]]] = sys .stderr ,
139- raise_on_fail : bool = True ,
140- log_run_to_stderr : bool = True ,
141- abbreviate_non_option_arguments : bool = False ,
142- ** kwargs ) -> CommandOutput :
120+ def run_cmd (
121+ * cmd : Optional [str ],
122+ out : Optional [Union [TeeCapture , IO [str ]]] = sys .stdout ,
123+ err : Optional [Union [TeeCapture , IO [str ]]] = sys .stderr ,
124+ raise_on_fail : bool = True ,
125+ log_run_to_stderr : bool = True ,
126+ abbreviate_non_option_arguments : bool = False ,
127+ ** kwargs ,
128+ ) -> CommandOutput :
143129 """Invokes a subprocess and waits for it to finish.
144130
145131 Args:
@@ -182,23 +168,44 @@ def run_cmd(*cmd: Optional[str],
182168 if abbreviate_non_option_arguments :
183169 cmd_desc = abbreviate_command_arguments_after_switches (cmd_desc )
184170 print ('run:' , cmd_desc , file = sys .stderr )
185- result = asyncio .get_event_loop ().run_until_complete (
186- _async_wait_for_process (
187- asyncio .create_subprocess_exec (* kept_cmd ,
188- stdout = asyncio .subprocess .PIPE ,
189- stderr = asyncio .subprocess .PIPE ,
190- ** kwargs ), out , err ))
171+
172+ loop = None
173+ try :
174+ loop = asyncio .get_running_loop ()
175+ except RuntimeError :
176+ loop = asyncio .new_event_loop ()
177+ asyncio .set_event_loop (loop )
178+
179+ try :
180+ result = loop .run_until_complete (
181+ _async_wait_for_process (
182+ asyncio .create_subprocess_exec (
183+ * kept_cmd ,
184+ stdout = asyncio .subprocess .PIPE ,
185+ stderr = asyncio .subprocess .PIPE ,
186+ ** kwargs ,
187+ ),
188+ out ,
189+ err ,
190+ )
191+ )
192+ finally :
193+ if loop != None :
194+ loop .close ()
195+
191196 if raise_on_fail and result [2 ]:
192197 raise subprocess .CalledProcessError (result [2 ], kept_cmd )
193198 return result
194199
195200
196- def run_shell (cmd : str ,
197- out : Optional [Union [TeeCapture , IO [str ]]] = sys .stdout ,
198- err : Optional [Union [TeeCapture , IO [str ]]] = sys .stderr ,
199- raise_on_fail : bool = True ,
200- log_run_to_stderr : bool = True ,
201- ** kwargs ) -> CommandOutput :
201+ def run_shell (
202+ cmd : str ,
203+ out : Optional [Union [TeeCapture , IO [str ]]] = sys .stdout ,
204+ err : Optional [Union [TeeCapture , IO [str ]]] = sys .stderr ,
205+ raise_on_fail : bool = True ,
206+ log_run_to_stderr : bool = True ,
207+ ** kwargs ,
208+ ) -> CommandOutput :
202209 """Invokes a shell command and waits for it to finish.
203210
204211 Args:
@@ -233,12 +240,28 @@ def run_shell(cmd: str,
233240 """
234241 if log_run_to_stderr :
235242 print ('shell:' , cmd , file = sys .stderr )
236- result = asyncio .get_event_loop ().run_until_complete (
237- _async_wait_for_process (
238- asyncio .create_subprocess_shell (cmd ,
239- stdout = asyncio .subprocess .PIPE ,
240- stderr = asyncio .subprocess .PIPE ,
241- ** kwargs ), out , err ))
243+
244+ loop = None
245+ try :
246+ loop = asyncio .get_running_loop ()
247+ except RuntimeError :
248+ loop = asyncio .new_event_loop ()
249+ asyncio .set_event_loop (loop )
250+
251+ try :
252+ result = loop .run_until_complete (
253+ _async_wait_for_process (
254+ asyncio .create_subprocess_shell (
255+ cmd , stdout = asyncio .subprocess .PIPE , stderr = asyncio .subprocess .PIPE , ** kwargs
256+ ),
257+ out ,
258+ err ,
259+ )
260+ )
261+ finally :
262+ if loop != None :
263+ loop .close ()
264+
242265 if raise_on_fail and result [2 ]:
243266 raise subprocess .CalledProcessError (result [2 ], cmd )
244267 return result
@@ -261,9 +284,7 @@ def output_of(*cmd: Optional[str], **kwargs) -> str:
261284 subprocess.CalledProcessError: The process returned a non-zero error
262285 code and raise_on_fail was set.
263286 """
264- result = cast (
265- str ,
266- run_cmd (* cmd , log_run_to_stderr = False , out = TeeCapture (), ** kwargs ).out )
287+ result = cast (str , run_cmd (* cmd , log_run_to_stderr = False , out = TeeCapture (), ** kwargs ).out )
267288
268289 # Strip final newline.
269290 if result .endswith ('\n ' ):
0 commit comments