Skip to content

Commit 05384ce

Browse files
committed
ci: build only the Zephyr boards that have the changed module
ci_set_matrix.py cannot ask make which Zephyr boards compile a module, so a change to any module built all 29 of them, and a frozen library update too although the port has no frozen modules. The module table of a Zephyr board is only known to its build, which writes it to autogen_board_info.toml. Keep it: build_release_files.py copies the file the push build generated to bin/zephyr-modules/<ref>/ <board>.toml, from where the existing bin/ upload puts it on S3 next to mpy-cross and the stubs. The scheduler fetches it for the PR's base ref and builds a board when the table says the module is enabled, or when the table is missing, unreadable or does not know the module. frozen/ changes build no Zephyr boards; supervisor/ changes still build all. Until the first push build after this lands there is no table and every Zephyr board is built, as today. ZEPHYR_MODULES_URL overrides the location for testing.
1 parent dea8844 commit 05384ce

2 files changed

Lines changed: 72 additions & 4 deletions

File tree

‎tools/build_release_files.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,22 @@
3232

3333
build_all = os.environ.get("GITHUB_EVENT_NAME") != "pull_request"
3434

35+
36+
def save_zephyr_board_info(board, board_dir):
37+
"""Keep the module table a Zephyr build generated, under bin/ so it is uploaded.
38+
39+
tools/ci_set_matrix.py reads it from S3 to decide which Zephyr boards a change
40+
to a module concerns.
41+
"""
42+
ref = os.environ.get("GITHUB_REF_NAME")
43+
board_info = board_dir / "autogen_board_info.toml"
44+
if not ref or not board_info.exists():
45+
return
46+
destination = pathlib.Path("../bin/zephyr-modules") / ref
47+
destination.mkdir(parents=True, exist_ok=True)
48+
shutil.copyfile(board_info, destination / f"{board}.toml")
49+
50+
3551
LANGUAGE_FIRST = "en_US"
3652
LANGUAGE_THRESHOLD = 10 * 1024
3753

@@ -61,7 +77,9 @@
6177
board_settings = {"CLEAN_REBUILD_LANGUAGES": []}
6278
with cp_toml.open("rb") as f:
6379
board_settings.update(tomllib.load(f))
80+
zephyr_board_dir = cp_toml.parent
6481
else:
82+
zephyr_board_dir = None
6583
board_settings = get_settings_from_makefile("../ports/" + board_info["port"], board)
6684
board_settings["CIRCUITPY_BUILD_EXTENSIONS"] = [
6785
extension.strip()
@@ -141,6 +159,9 @@
141159
if exit_status == 0:
142160
exit_status = 1
143161

162+
if zephyr_board_dir and language == LANGUAGE_FIRST and make_result.returncode == 0:
163+
save_zephyr_board_info(board, zephyr_board_dir)
164+
144165
print(
145166
"Build {board} for {language}{clean_build} took {build_duration:.2f}s and {success}".format(
146167
board=board,

‎tools/ci_set_matrix.py‎

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import json
2929
import pathlib
3030
import subprocess
31+
import tomllib
32+
import urllib.request
3133
from concurrent.futures import ThreadPoolExecutor
3234

3335
tools_dir = pathlib.Path(__file__).resolve().parent
@@ -71,6 +73,51 @@
7173

7274
GITHUB_MATRIX_LIMIT = 256
7375

76+
# Zephyr boards don't use make, so their module tables can't be computed here. Each push
77+
# build uploads the board's autogen_board_info.toml with the firmware
78+
# (tools/build_release_files.py); a board whose table is missing, unreadable or does not
79+
# know a module is built.
80+
ZEPHYR_MODULES_URL = os.environ.get(
81+
"ZEPHYR_MODULES_URL",
82+
"https://adafruit-circuit-python.s3.amazonaws.com/bin/zephyr-modules/{ref}/{board}.toml",
83+
)
84+
zephyr_modules = {}
85+
86+
87+
def fetch_zephyr_modules(boards):
88+
ref = os.environ.get("GITHUB_BASE_REF") or os.environ.get("GITHUB_REF_NAME") or "main"
89+
90+
def fetch(board):
91+
url = ZEPHYR_MODULES_URL.format(board=board, ref=ref)
92+
try:
93+
with urllib.request.urlopen(url, timeout=10) as response:
94+
return board, tomllib.loads(response.read().decode("utf-8"))["modules"]
95+
except Exception as e: # noqa: BLE001 -- whatever went wrong, the board gets built
96+
print(f" {board}: no module table ({e})")
97+
return board, None
98+
99+
need = [board for board in boards if board not in zephyr_modules]
100+
with ThreadPoolExecutor(max_workers=os.cpu_count()) as ex:
101+
zephyr_modules.update(ex.map(fetch, need))
102+
103+
104+
def zephyr_boards_for(file, module, boards):
105+
"""The Zephyr boards a change to `file` concerns, out of `boards`."""
106+
if file.startswith("frozen"):
107+
# The port has no frozen modules.
108+
return []
109+
if module is None:
110+
return boards
111+
fetch_zephyr_modules(boards)
112+
selected = [
113+
board
114+
for board in boards
115+
if zephyr_modules.get(board) is None or zephyr_modules[board].get(module, True)
116+
]
117+
print(f"Zephyr boards with {module}: {len(selected)} of {len(boards)}")
118+
return selected
119+
120+
74121
PATTERN_WINDOWS = {
75122
".github/",
76123
"extmod/",
@@ -205,10 +252,10 @@ def get_settings(board):
205252
# the logic to build all boards breaks.
206253
boards = set(port_to_board[port] if port else all_board_ids)
207254

208-
# Zephyr boards don't use make, so build them and don't compute their settings.
209-
for board in port_to_board["zephyr-cp"]:
210-
if board in boards:
211-
boards_to_build.add(board)
255+
zephyr_boards = sorted(boards & port_to_board["zephyr-cp"])
256+
module = module_matches.group(2) if module_matches else None
257+
boards_to_build.update(zephyr_boards_for(file, module, zephyr_boards))
258+
boards -= port_to_board["zephyr-cp"]
212259

213260
for board in boards_to_build:
214261
if board in boards:

0 commit comments

Comments
 (0)