|
2 | 2 | import subprocess |
3 | 3 | import sys |
4 | 4 | from pathlib import Path |
5 | | -from typing import List |
| 5 | +from threading import RLock |
| 6 | +from typing import List, Optional |
6 | 7 |
|
7 | 8 | import distro |
8 | 9 | import i18n |
|
15 | 16 | containers agree on it. |
16 | 17 | """ |
17 | 18 |
|
18 | | -current_command = None |
| 19 | + |
| 20 | +class _CurrentCommand: |
| 21 | + """Contains information on the current command being run as a subprocess, if one |
| 22 | + exists. |
| 23 | + """ |
| 24 | + |
| 25 | + _process: Optional[subprocess.Popen] = None |
| 26 | + _lock = RLock() |
| 27 | + _interrupted = False |
| 28 | + |
| 29 | + @property |
| 30 | + def process(self) -> Optional[subprocess.Popen]: |
| 31 | + """ |
| 32 | + :return: The currently running subprocess, or None if no subprocess is running |
| 33 | + """ |
| 34 | + with self._lock: |
| 35 | + return self._process |
| 36 | + |
| 37 | + @process.setter |
| 38 | + def process(self, value: subprocess.Popen) -> None: |
| 39 | + with self._lock: |
| 40 | + if self._process is not None and self._process.poll() is None: |
| 41 | + # This is never expected to happen, as subprocesses are run in serial |
| 42 | + # and in a blocking fashion |
| 43 | + raise RuntimeError("Only one process may be run at once") |
| 44 | + |
| 45 | + self._process = value |
| 46 | + |
| 47 | + @property |
| 48 | + def interrupted(self) -> bool: |
| 49 | + """ |
| 50 | + :return: If true, the subprocess was interrupted by a signal |
| 51 | + """ |
| 52 | + return self._interrupted |
| 53 | + |
| 54 | + def send_signal(self, sig: int): |
| 55 | + """ |
| 56 | + :param sig: The signal to send to the subprocess |
| 57 | + """ |
| 58 | + with self._lock: |
| 59 | + if self._process is None: |
| 60 | + message = ( |
| 61 | + "Attempted to send a signal when no process was running" |
| 62 | + ) |
| 63 | + raise RuntimeError(message) |
| 64 | + self._interrupted = True |
| 65 | + self._process.send_signal(sig) |
| 66 | + |
| 67 | + |
| 68 | +current_command = _CurrentCommand() |
19 | 69 |
|
20 | 70 |
|
21 | 71 | def create_group(group_name: str, group_id: int): |
@@ -103,13 +153,18 @@ def run( |
103 | 153 | """ |
104 | 154 | if print_command: |
105 | 155 | print_utils.print_color(" ".join(command), print_utils.Color.MAGENTA) |
106 | | - cmd = subprocess.Popen(command, *args, **kwargs) |
107 | | - global current_command |
108 | | - current_command = cmd |
109 | | - cmd.wait() |
110 | | - if cmd.returncode != 0 and exit_on_failure: |
111 | | - sys.exit(cmd.returncode) |
112 | | - return cmd |
| 156 | + |
| 157 | + current_command.process = subprocess.Popen(command, *args, **kwargs) |
| 158 | + current_command.process.wait() |
| 159 | + |
| 160 | + if current_command.interrupted: |
| 161 | + # A signal was sent to the command before it finished |
| 162 | + print_utils.fail_translate("general.interrupted") |
| 163 | + elif current_command.process.returncode != 0 and exit_on_failure: |
| 164 | + # The command failed during normal execution |
| 165 | + sys.exit(current_command.process.returncode) |
| 166 | + |
| 167 | + return current_command.process |
113 | 168 |
|
114 | 169 |
|
115 | 170 | _SUPPORTED_DISTROS = { |
|
0 commit comments