Skip to content

Commit 167c540

Browse files
Merge pull request #85131 from charles-zablit/charles-zablit/update-checkout/type-arguments
[update-checkout] add typing to git invocation exceptions
2 parents 9c9f6b7 + 186e76e commit 167c540

File tree

5 files changed

+142
-91
lines changed

5 files changed

+142
-91
lines changed

utils/update_checkout/tests/test_locked_repository.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def _update_arguments_with_fake_path(repo_name: str, path: str) -> UpdateArgumen
1616
reset_to_remote=False,
1717
clean=False,
1818
stash=False,
19-
cross_repos_pr=False,
19+
cross_repos_pr={},
2020
output_prefix="",
2121
verbose=False,
2222
)

utils/update_checkout/update_checkout/git_command.py

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,44 @@
1+
import os
12
import shlex
23
import subprocess
34
import sys
4-
from typing import List, Any, Optional, Dict
5+
from typing import List, Any, Optional, Dict, Tuple
6+
7+
8+
class GitException(Exception):
9+
"""
10+
Exception raised when a Git command execution fails.
11+
12+
Attributes
13+
----------
14+
returncode : int
15+
The return code from the failed Git command.
16+
command : List[str]
17+
The Git command that was executed.
18+
repo_name : str
19+
The name of the Git repository.
20+
stderr : str
21+
The output of the failed Git command.
22+
"""
23+
24+
def __init__(
25+
self,
26+
returncode: int,
27+
command: List[str],
28+
repo_name: str,
29+
output: str,
30+
):
31+
super().__init__()
32+
self.returncode = returncode
33+
self.command = command
34+
self.repo_name = repo_name
35+
self.stderr = output
36+
37+
def __str__(self):
38+
return (
39+
f"[{self.repo_name}] '{Git._quote_command(self.command)}' "
40+
f"returned ({self.returncode}) with the following {self.stderr}."
41+
)
542

643

744
class Git:
@@ -15,9 +52,9 @@ def run(
1552
allow_non_zero_exit: bool = False,
1653
fatal: bool = False,
1754
**kwargs,
18-
):
55+
) -> Tuple[str, int, List[str]]:
1956
command = Git._build_command(args)
20-
57+
output = ""
2158
try:
2259
result = subprocess.run(
2360
command,
@@ -38,20 +75,15 @@ def run(
3875
if fatal:
3976
sys.exit(
4077
f"command `{command}` terminated with a non-zero exit "
41-
f"status {str(e.returncode)}, aborting")
42-
eout = Exception(
43-
f"[{repo_path}] '{Git._quote_command(command)}' failed with '{output}'"
78+
f"status {str(e.returncode)}, aborting"
79+
)
80+
raise GitException(
81+
e.returncode, command, os.path.dirname(repo_path), output
4482
)
45-
eout.ret = e.returncode
46-
eout.arguments = command
47-
eout.repo_path = repo_path
48-
eout.stderr = output
49-
raise eout
5083
except OSError as e:
5184
if fatal:
5285
sys.exit(
53-
f"could not execute '{Git._quote_command(command)}': "
54-
f"{e.strerror}"
86+
f"could not execute '{Git._quote_command(command)}': {e.strerror}"
5587
)
5688
return (output.strip(), result.returncode, command)
5789

@@ -73,7 +105,7 @@ def _echo_command(
73105
print(f"{prefix}+ {' '.join(command_str)}", file=sys.stderr)
74106
if output:
75107
for line in output.splitlines():
76-
print(prefix+line)
108+
print(prefix + line)
77109
sys.stdout.flush()
78110
sys.stderr.flush()
79111

@@ -86,5 +118,5 @@ def _quote(arg: Any) -> str:
86118
return shlex.quote(str(arg))
87119

88120
@staticmethod
89-
def _quote_command(command: Any) -> str:
121+
def _quote_command(command: List[Any]) -> str:
90122
return " ".join(Git._quote(arg) for arg in command)

utils/update_checkout/update_checkout/parallel_runner.py

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import sys
22
from multiprocessing import cpu_count
33
import time
4-
from typing import Callable, List, Any, Tuple, Union
4+
from typing import Callable, List, Any, Optional, Tuple, Union
55
from threading import Lock, Thread, Event
66
from concurrent.futures import ThreadPoolExecutor
77
import shutil
88

9-
from .runner_arguments import RunnerArguments, AdditionalSwiftSourcesArguments
9+
from .git_command import GitException
10+
11+
from .runner_arguments import RunnerArguments, AdditionalSwiftSourcesArguments, UpdateArguments
1012

1113

1214
class TaskTracker:
@@ -50,32 +52,38 @@ def done_task_counter(self) -> int:
5052
class MonitoredFunction:
5153
def __init__(
5254
self,
53-
fn: Callable,
55+
fn: Callable[..., Union[Exception]],
5456
task_tracker: TaskTracker,
5557
):
56-
self.fn = fn
58+
self._fn = fn
5759
self._task_tracker = task_tracker
5860

5961
def __call__(self, *args: Union[RunnerArguments, AdditionalSwiftSourcesArguments]):
6062
task_name = args[0].repo_name
6163
self._task_tracker.mark_task_as_running(task_name)
6264
result = None
6365
try:
64-
result = self.fn(*args)
66+
result = self._fn(*args)
6567
except Exception as e:
6668
print(e)
6769
finally:
6870
self._task_tracker.mark_task_as_done(task_name)
6971
return result
7072

7173

72-
class ParallelRunner:
74+
class ParallelRunner():
7375
def __init__(
7476
self,
75-
fn: Callable,
76-
pool_args: List[Union[RunnerArguments, AdditionalSwiftSourcesArguments]],
77+
fn: Callable[..., None],
78+
pool_args: Union[List[UpdateArguments], List[AdditionalSwiftSourcesArguments]],
7779
n_threads: int = 0,
7880
):
81+
def run_safely(*args, **kwargs):
82+
try:
83+
fn(*args, **kwargs)
84+
except GitException as e:
85+
return e
86+
7987
if n_threads == 0:
8088
# Limit the number of threads as the performance regresses if the
8189
# number is too high.
@@ -84,7 +92,8 @@ def __init__(
8492
self._monitor_polling_period = 0.1
8593
self._terminal_width = shutil.get_terminal_size().columns
8694
self._pool_args = pool_args
87-
self._fn = fn
95+
self._fn_name = fn.__name__
96+
self._fn = run_safely
8897
self._output_prefix = pool_args[0].output_prefix
8998
self._nb_repos = len(pool_args)
9099
self._stop_event = Event()
@@ -93,8 +102,8 @@ def __init__(
93102
self._task_tracker = TaskTracker()
94103
self._monitored_fn = MonitoredFunction(self._fn, self._task_tracker)
95104

96-
def run(self) -> List[Any]:
97-
print(f"Running ``{self._fn.__name__}`` with up to {self._n_threads} processes.")
105+
def run(self) -> List[Union[None, Exception]]:
106+
print(f"Running ``{self._fn_name}`` with up to {self._n_threads} processes.")
98107
if self._verbose:
99108
with ThreadPoolExecutor(max_workers=self._n_threads) as pool:
100109
results = list(pool.map(self._fn, self._pool_args, timeout=1800))
@@ -129,13 +138,10 @@ def _monitor(self):
129138
sys.stdout.flush()
130139

131140
@staticmethod
132-
def check_results(results, op) -> int:
133-
"""Function used to check the results of ParallelRunner.
134-
135-
NOTE: This function was originally located in the shell module of
136-
swift_build_support and should eventually be replaced with a better
137-
parallel implementation.
138-
"""
141+
def check_results(
142+
results: Optional[List[Union[GitException, Exception, Any]]], operation: str
143+
) -> int:
144+
"""Check the results of ParallelRunner and print the failures."""
139145

140146
fail_count = 0
141147
if results is None:
@@ -144,15 +150,10 @@ def check_results(results, op) -> int:
144150
if r is None:
145151
continue
146152
if fail_count == 0:
147-
print("======%s FAILURES======" % op)
153+
print(f"======{operation} FAILURES======")
148154
fail_count += 1
149-
if isinstance(r, str):
155+
if isinstance(r, (GitException, Exception)):
150156
print(r)
151157
continue
152-
if not hasattr(r, "repo_path"):
153-
# TODO: create a proper Exception class with these attributes
154-
continue
155-
print("%s failed (ret=%d): %s" % (r.repo_path, r.ret, r))
156-
if r.stderr:
157-
print(r.stderr.decode())
158+
print(r)
158159
return fail_count

utils/update_checkout/update_checkout/runner_arguments.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from dataclasses import dataclass
2-
from typing import Any, Dict, List
2+
from typing import Any, Dict, List, Optional
33

44
from .cli_arguments import CliArguments
55

@@ -15,12 +15,12 @@ class UpdateArguments(RunnerArguments):
1515
source_root: str
1616
config: Dict[str, Any]
1717
scheme_map: Any
18-
tag: str
18+
tag: Optional[str]
1919
timestamp: Any
2020
reset_to_remote: bool
2121
clean: bool
2222
stash: bool
23-
cross_repos_pr: bool
23+
cross_repos_pr: Dict[str, str]
2424

2525
@dataclass
2626
class AdditionalSwiftSourcesArguments(RunnerArguments):

0 commit comments

Comments
 (0)