Skip to content

Commit b5354e5

Browse files
authored
Merge pull request #342 from tekktrik/dev/ci-rerun-update
Tooling update
2 parents da879a8 + 05b5de5 commit b5354e5

File tree

4 files changed

+247
-14
lines changed

4 files changed

+247
-14
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ _build
1111
env.sh
1212
*.swp
1313
.libraries/*
14+
.gitlibs/*
1415
.cp_org/*
1516
.blinka/*
1617
.vscode

tools/ci_status.py

+177-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616

1717
from typing import Optional
1818
import argparse
19+
import time
1920
from github.Repository import Repository
2021
from github.Workflow import Workflow
22+
from github.WorkflowRun import WorkflowRun
2123
from github.GithubException import GithubException
2224
from library_functions import StrPath
2325
from iterate_libraries import (
@@ -56,6 +58,46 @@ def run_gh_rest_check(
5658
return workflow_runs[0].conclusion
5759

5860

61+
def run_gh_rest_rerun(
62+
lib_repo: Repository,
63+
user: Optional[str] = None,
64+
branch: Optional[str] = None,
65+
workflow_filename: Optional[str] = "build.yml",
66+
rerun_level: int = 0,
67+
) -> bool:
68+
"""Uses ``PyGithub`` to rerun the CI status of a repository
69+
70+
:param Repository lib_repo: The repo as a github.Repository.Repository object
71+
:param str|None user: The user that triggered the run; if `None` is
72+
provided, any user is acceptable
73+
:param str|None branch: The branch name to specifically check; if `None` is
74+
provided, all branches are allowed; this is the default
75+
:param str|None workflow_filename: The filename of the workflow; if `None` is
76+
provided, any workflow name is acceptable; the default is ``"build.yml"``
77+
:param int rerun_level: The level at which rerun should occur (0 = none,
78+
1 = failed, 2 = all)
79+
:return: The requested runs conclusion
80+
:rtype: bool
81+
"""
82+
if not rerun_level:
83+
return False
84+
if rerun_level == 1:
85+
result = (
86+
run_gh_rest_check(lib_repo, user, branch, workflow_filename) == "success"
87+
)
88+
if rerun_level == 2 or not result:
89+
arg_dict = {}
90+
if user is not None:
91+
arg_dict["actor"] = user
92+
if branch is not None:
93+
arg_dict["branch"] = branch
94+
workflow: Workflow = lib_repo.get_workflow(workflow_filename)
95+
latest_run: WorkflowRun = workflow.get_runs(**arg_dict)[0]
96+
latest_run.rerun()
97+
return True
98+
return False
99+
100+
59101
def check_build_status(
60102
lib_repo: Repository,
61103
user: Optional[str] = None,
@@ -105,13 +147,63 @@ def check_build_status(
105147
return None
106148

107149

150+
# pylint: disable=too-many-arguments
151+
def rerun_workflow(
152+
lib_repo: Repository,
153+
user: Optional[str] = None,
154+
branch: Optional[str] = None,
155+
workflow_filename: Optional[str] = "build.yml",
156+
rerun_level: int = 0,
157+
debug: bool = False,
158+
):
159+
"""Uses ``PyGithub`` to rerun the CI of the Adafruit
160+
CircuitPython Bundle repositories
161+
162+
:param Repository lib_repo: The repo as a github.Repository.Repository object
163+
:param str|None user: The user that triggered the run; if `None` is
164+
provided, any user is acceptable
165+
:param str|None branch: The branch name to specifically check; if `None` is
166+
provided, all branches are allowed; this is the default
167+
:param str|None workflow_filename: The filename of the workflow; if `None`
168+
is provided, any workflow name is acceptable; the defail is `"build.yml"`
169+
:param int rerun_level: The level at which rerun should occur (0 = none,
170+
1 = failed, 2 = all)
171+
:param bool debug: Whether debug statements should be printed to the standard
172+
output
173+
:return: The result of the workflow run, or ``None`` if it could not be
174+
determined
175+
:rtype: bool|None
176+
"""
177+
if lib_repo.archived:
178+
return False
179+
180+
try:
181+
result = run_gh_rest_rerun(
182+
lib_repo, user, branch, workflow_filename, rerun_level
183+
)
184+
if debug and result:
185+
print("***", "Library", lib_repo.name, "workflow was rerun!", "***")
186+
return result
187+
except GithubException:
188+
if debug:
189+
print(
190+
"???",
191+
"Library",
192+
lib_repo.name,
193+
"had an issue occur",
194+
"???",
195+
)
196+
return None
197+
198+
108199
def check_build_statuses(
109200
gh_token: str,
110201
user: Optional[str] = None,
111202
branch: Optional[str] = "main",
112203
workflow_filename: Optional[str] = "build.yml",
113204
*,
114205
debug: bool = False,
206+
local_folder: str = "",
115207
) -> list[RemoteLibFunc_IterResult[bool]]:
116208
"""Checks all the libraries in the Adafruit CircuitPython Bundle to get the
117209
latest build status with the requested information
@@ -125,6 +217,7 @@ def check_build_statuses(
125217
provided, any workflow name is acceptable; the defail is `"build.yml"`
126218
:param bool debug: Whether debug statements should be printed to
127219
the standard output
220+
:param str local_folder: A path to a local folder containing extra repositories
128221
:return: A list of tuples containing paired Repoistory objects and build
129222
statuses
130223
:rtype: list
@@ -133,6 +226,49 @@ def check_build_statuses(
133226
return iter_remote_bundle_with_func(
134227
gh_token,
135228
[(check_build_status, (user, branch, workflow_filename), {"debug": debug})],
229+
local_folder=local_folder,
230+
)
231+
232+
233+
def rerun_workflows(
234+
gh_token: str,
235+
user: Optional[str] = None,
236+
branch: Optional[str] = "main",
237+
workflow_filename: Optional[str] = "build.yml",
238+
rerun_level: int = 0,
239+
*,
240+
debug: bool = False,
241+
local_folder: str = "",
242+
) -> list[RemoteLibFunc_IterResult[bool]]:
243+
"""Reruns the CI of all the libraries in the Adafruit CircuitPython Bundle.
244+
245+
:param str gh_token: The Github token to be used for with the Github API
246+
:param str|None user: The user that triggered the run; if `None` is
247+
provided, any user is acceptable
248+
:param str|None branch: The branch name to specifically check; if `None` is
249+
provided, all branches are allowed; this is the default
250+
:param str|None workflow_filename: The filename of the workflow; if `None` is
251+
provided, any workflow name is acceptable; the defail is `"build.yml"`
252+
:param int rerun_level: The level at which reruns should occur (0 = none,
253+
1 = failed, 2 = all)
254+
:param bool debug: Whether debug statements should be printed to
255+
the standard output
256+
:param str local_folder: A path to a local folder containing extra repositories
257+
:return: A list of tuples containing paired Repoistory objects and build
258+
statuses
259+
:rtype: list
260+
"""
261+
262+
return iter_remote_bundle_with_func(
263+
gh_token,
264+
[
265+
(
266+
rerun_workflow,
267+
(user, branch, workflow_filename, rerun_level),
268+
{"debug": debug},
269+
)
270+
],
271+
local_folder=local_folder,
136272
)
137273

138274

@@ -193,12 +329,52 @@ def save_build_statuses(
193329
parser.add_argument(
194330
"--debug", action="store_true", help="Print debug text during execution"
195331
)
332+
parser.add_argument(
333+
"--rerun-level",
334+
metavar="R",
335+
type=int,
336+
dest="rerun_level",
337+
default=0,
338+
help="Level to rerun CI workflows (0 = none, 1 = failed, 2 = all)",
339+
)
340+
parser.add_argument(
341+
"--local-folder",
342+
metavar="L",
343+
type=str,
344+
dest="local_folder",
345+
default="",
346+
help="An additional folder to check and run",
347+
)
196348

197349
args = parser.parse_args()
198350

351+
if args.rerun_level:
352+
if args.debug:
353+
print("Rerunning workflows...")
354+
rerun_workflows(
355+
args.gh_token,
356+
args.user,
357+
args.branch,
358+
args.workflow,
359+
args.rerun_level,
360+
debug=args.debug,
361+
local_folder=args.local_folder,
362+
)
363+
if args.debug:
364+
print("Waiting 10 minutes to allow workflows to finish running...")
365+
time.sleep(600)
366+
367+
if args.debug:
368+
print("Checking workflows statuses...")
199369
results = check_build_statuses(
200-
args.gh_token, args.user, args.branch, args.workflow, debug=args.debug
370+
args.gh_token,
371+
args.user,
372+
args.branch,
373+
args.workflow,
374+
debug=args.debug,
375+
local_folder=args.local_folder,
201376
)
377+
202378
fail_list = [
203379
repo_name.name for repo_name, repo_results in results if not repo_results[0]
204380
]

tools/iterate_libraries.py

+52-13
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616

1717
import os
1818
import glob
19+
import pathlib
1920
from collections.abc import Sequence, Iterable
20-
from typing import TypeVar
21+
from typing import TypeVar, Any, Union, List
2122
from typing_extensions import TypeAlias
2223
import parse
2324
from github import Github
@@ -65,9 +66,25 @@
6566
_BUNDLE_BRANCHES = ("drivers", "helpers")
6667

6768

69+
def perform_func(
70+
item: Any,
71+
func_workflow: Union[RemoteLibFunc_IterInstruction, LocalLibFunc_IterInstruction],
72+
) -> Union[List[RemoteLibFunc_IterResult], List[LocalLibFunc_IterResult]]:
73+
"""
74+
Perform the given function
75+
"""
76+
func_results = []
77+
for func, args, kwargs in func_workflow:
78+
result = func(item, *args, **kwargs)
79+
func_results.append(result)
80+
return func_results
81+
82+
6883
def iter_local_bundle_with_func(
6984
bundle_path: StrPath,
7085
func_workflow: Iterable[LocalLibFunc_IterInstruction],
86+
*,
87+
local_folder: str = "",
7188
) -> list[LocalLibFunc_IterResult]:
7289
"""Iterate through the libraries and run a given function with the
7390
provided arguments
@@ -85,6 +102,9 @@ def iter_local_bundle_with_func(
85102
# Initialize list of results
86103
results = []
87104

105+
# Keep track of all libraries iterated
106+
iterated = set()
107+
88108
# Loop through each bundle branch
89109
for branch_name in _BUNDLE_BRANCHES:
90110

@@ -94,20 +114,30 @@ def iter_local_bundle_with_func(
94114
# Enter each library in the bundle
95115
for library_path in libraries_path_list:
96116

97-
func_results = []
98-
99-
for func, args, kwargs in func_workflow:
100-
result = func(library_path, *args, **kwargs)
101-
func_results.append(result)
117+
iterated.add(os.path.split(library_path)[1].lower())
118+
func_results = perform_func(library_path, func_workflow)
102119

103120
results.append((library_path, func_results))
104121

122+
if local_folder:
123+
additional = {
124+
os.path.split(pathname)[1].lower()
125+
for pathname in glob.glob(os.path.join(local_folder, "*"))
126+
}
127+
diff = additional.difference(iterated)
128+
for unused in diff:
129+
unused_func_results = perform_func(unused, func_workflow)
130+
results.append((unused, unused_func_results))
131+
105132
return results
106133

107134

108135
# pylint: disable=too-many-locals
109136
def iter_remote_bundle_with_func(
110-
gh_token: str, func_workflow: RemoteLibFunc_IterInstruction
137+
gh_token: str,
138+
func_workflow: RemoteLibFunc_IterInstruction,
139+
*,
140+
local_folder: str = "",
111141
) -> list[RemoteLibFunc_IterResult]:
112142
"""Iterate through the remote bundle, accessing each library's git repo
113143
using the GitHub RESTful API (specifically using ``PyGithub``)
@@ -129,6 +159,9 @@ def iter_remote_bundle_with_func(
129159
# Initialize list of results
130160
results = []
131161

162+
# Keep track of all libraries iterated
163+
iterated = set()
164+
132165
# Loop through each bundle branch
133166
for branch_name in _BUNDLE_BRANCHES:
134167

@@ -144,13 +177,19 @@ def iter_remote_bundle_with_func(
144177
repo_name: str = repo_name_result.named["repo_name"]
145178

146179
repo = github_client.get_repo(f"adafruit/{repo_name}")
180+
iterated.add(repo_name.lower())
147181

148-
func_results = []
149-
150-
for func, args, kwargs in func_workflow:
151-
result = func(repo, *args, **kwargs)
152-
func_results.append(result)
153-
182+
func_results = perform_func(repo, func_workflow)
154183
results.append((repo, func_results))
155184

185+
if local_folder:
186+
additional = {
187+
path.name.lower() for path in pathlib.Path(local_folder).glob("*")
188+
}
189+
diff = additional.difference(iterated)
190+
for unused in diff:
191+
unused_repo = github_client.get_repo(f"adafruit/{unused}")
192+
unused_func_results = perform_func(unused_repo, func_workflow)
193+
results.append((unused_repo, unused_func_results))
194+
156195
return results

tools/run_black.sh

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# SPDX-FileCopyrightText: 2023 Alec Delaney
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
rm -rf .gitlibs
6+
mkdir .gitlibs
7+
cd .libraries
8+
for repo in *; do
9+
cd ../.gitlibs
10+
git clone https://github.com/adafruit/$repo.git
11+
cd $repo
12+
pre-commit run --all-files
13+
git add -A
14+
git commit -m "Run pre-commit"
15+
git push
16+
cd ..
17+
done

0 commit comments

Comments
 (0)