summaryrefslogtreecommitdiff
path: root/.github/scripts/ci_set_matrix.py
diff options
context:
space:
mode:
Diffstat (limited to '.github/scripts/ci_set_matrix.py')
-rwxr-xr-x.github/scripts/ci_set_matrix.py193
1 files changed, 193 insertions, 0 deletions
diff --git a/.github/scripts/ci_set_matrix.py b/.github/scripts/ci_set_matrix.py
new file mode 100755
index 000000000..409e6dbc1
--- /dev/null
+++ b/.github/scripts/ci_set_matrix.py
@@ -0,0 +1,193 @@
+#!/usr/bin/env python3
+import argparse
+import json
+import os
+import subprocess
+import sys
+
+# toolchain, url
+toolchain_list = [
+ "aarch64-gcc",
+ "arm-clang",
+ "arm-iar",
+ "arm-gcc",
+ "esp-idf",
+ "ft9xx-gcc",
+ "msp430-gcc",
+ "riscv-gcc",
+ "rx-gcc"
+]
+
+# family: [supported toolchain]
+family_list = {
+ "apm32f0xx": ["arm-gcc"],
+ "at32f402_405": ["arm-gcc"],
+ "at32f403a_407": ["arm-gcc"],
+ "at32f413": ["arm-gcc"],
+ "at32f415": ["arm-gcc"],
+ "at32f423": ["arm-gcc"],
+ "at32f425": ["arm-gcc"],
+ "at32f435_437": ["arm-gcc"],
+ "at32f45x": ["arm-gcc"],
+ "broadcom_32bit": ["arm-gcc"],
+ "broadcom_64bit": ["aarch64-gcc"],
+ "ch32f20x": ["arm-gcc"],
+ "ch32v10x": ["riscv-gcc"],
+ "ch32v20x": ["riscv-gcc"],
+ "ch32v30x": ["riscv-gcc"],
+ "ch583": ["riscv-gcc"],
+ "da1469x": ["arm-gcc"],
+ "fomu": ["riscv-gcc"],
+ "ft9xx": ["ft9xx-gcc"],
+ "gd32vf103": ["riscv-gcc"],
+ "hpmicro": ["riscv-gcc"],
+ "imxrt": ["arm-gcc", "arm-clang"],
+ "kinetis_k": ["arm-gcc"],
+ "kinetis_k32l": ["arm-gcc"],
+ "kinetis_kl": ["arm-gcc"],
+ "lpc11": ["arm-gcc", "arm-clang"],
+ "lpc13": ["arm-gcc", "arm-clang"],
+ "lpc15": ["arm-gcc", "arm-clang"],
+ "lpc17": ["arm-gcc", "arm-clang"],
+ "lpc18": ["arm-gcc", "arm-clang"],
+ "lpc40": ["arm-gcc", "arm-clang"],
+ "lpc43": ["arm-gcc", "arm-clang"],
+ "lpc51": ["arm-gcc", "arm-clang"],
+ "lpc54": ["arm-gcc", "arm-clang"],
+ "lpc55": ["arm-gcc", "arm-clang"],
+ "maxim": ["arm-gcc"],
+ "mcx": ["arm-gcc"],
+ "mm32": ["arm-gcc"],
+ "msp430": ["msp430-gcc"],
+ "msp432e4": ["arm-gcc"],
+ "nrf": ["arm-gcc", "arm-clang"],
+ "nuc100_120": ["arm-gcc"],
+ "nuc121_125": ["arm-gcc"],
+ "nuc126": ["arm-gcc"],
+ "nuc505": ["arm-gcc"],
+ "ra": ["arm-gcc"],
+ "rp2040": ["arm-gcc"],
+ "rw61x": ["arm-gcc"],
+ "rx": ["rx-gcc"],
+ "samd11": ["arm-gcc", "arm-clang"],
+ "samd2x_l2x": ["arm-gcc", "arm-clang"],
+ "samd5x_e5x": ["arm-gcc", "arm-clang"],
+ "samg": ["arm-gcc", "arm-clang"],
+ "stm32c0": ["arm-gcc", "arm-clang", "arm-iar"],
+ "stm32c5": ["arm-gcc", "arm-clang", "arm-iar"],
+ "stm32f0": ["arm-gcc", "arm-clang", "arm-iar"],
+ "stm32f1": ["arm-gcc", "arm-clang", "arm-iar"],
+ "stm32f2": ["arm-gcc", "arm-clang", "arm-iar"],
+ "stm32f3": ["arm-gcc", "arm-clang", "arm-iar"],
+ "stm32f4": ["arm-gcc", "arm-clang", "arm-iar"],
+ "stm32f7": ["arm-gcc", "arm-clang", "arm-iar"],
+ "stm32g0": ["arm-gcc", "arm-clang", "arm-iar"],
+ "stm32g4": ["arm-gcc", "arm-clang", "arm-iar"],
+ "stm32h5": ["arm-gcc", "arm-clang", "arm-iar"],
+ "stm32h7": ["arm-gcc", "arm-clang", "arm-iar"],
+ "stm32h7rs": ["arm-gcc", "arm-clang", "arm-iar"],
+ "stm32l0": ["arm-gcc", "arm-clang", "arm-iar"],
+ "stm32l4": ["arm-gcc", "arm-clang", "arm-iar"],
+ "stm32n6": ["arm-gcc"],
+ "stm32u0": ["arm-gcc", "arm-clang", "arm-iar"],
+ "stm32u5": ["arm-gcc", "arm-clang", "arm-iar"],
+ "stm32wb": ["arm-gcc", "arm-clang", "arm-iar"],
+ "stm32wba": ["arm-gcc", "arm-clang", "arm-iar"],
+ "tm4c": ["arm-gcc"],
+ "xmc4000": ["arm-gcc"],
+ # S3, P4 will be built by hil test
+ # "-bespressif_s3_devkitm": ["esp-idf"],
+ # "-bespressif_p4_function_ev": ["esp-idf"],
+}
+
+
+def set_matrix_json(select=None):
+ sel_fams = None
+ if select:
+ # every shape check is explicit: this runs AFTER main()'s fail-open handler, so
+ # an AttributeError on e.g. {"build": ["stm32f4"]} would red the step instead
+ # of falling back to the full matrix - the outcome that handler exists to prevent
+ b = select.get('build') if isinstance(select, dict) else None
+ if not isinstance(b, dict):
+ b = {}
+ if b.get('full') is False:
+ fams = b.get('families')
+ if not (isinstance(fams, list) and all(isinstance(f, str) for f in fams)):
+ # key ABSENT (or not a list of names) is an unusable selection, not
+ # "nothing selected": scoping every toolchain to [] would build zero
+ # families and report a vacuous green. An explicit families: [] stays a
+ # legitimate nothing-selected.
+ print('ci_set_matrix: UNSCOPED - build.full is false but the families '
+ 'list is unusable, emitting the full matrix', file=sys.stderr)
+ else:
+ sel_fams = set(fams)
+ matrix = {}
+ for toolchain in toolchain_list:
+ fams = [family for family, tc in family_list.items() if toolchain in tc]
+ if sel_fams is not None:
+ fams = [f for f in fams if f in sel_fams]
+ matrix[toolchain] = fams
+ if sel_fams:
+ # a family this file does not list builds on no toolchain, so it contributes no
+ # leg. hw/bsp holds several CI has never built (efm32, py32f0, same7x, ...) plus
+ # espressif, whose boards hil-build-esp builds by name.
+ # espressif is not a gap: its examples need the ESP-IDF environment
+ # (CLAUDE.md: `. "$IDF_PATH/export.sh"` before any build), which the cmake legs
+ # do not have - that is why it is commented out of family_list above. Its
+ # coverage comes from hil-build-esp, which builds those boards BY NAME in an IDF
+ # container, so an espressif-only PR is already validated and falling open to the
+ # full matrix would add 74 legs, none of which can compile espressif.
+ unbuilt = sorted(f for f in sel_fams if f not in family_list and f != 'espressif')
+ if unbuilt and not any(matrix.values()):
+ # NONE of the selected families is buildable here, so every leg would skip
+ # and the PR would go green from a build job that ran no compiler. That is
+ # an unusable selection, not "nothing selected": say UNSCOPED - which
+ # build.yml and .circleci/config.yml both grep for - and emit the full
+ # matrix. An explicit families: [] is still a legitimate nothing-selected,
+ # and a PARTIAL miss still scopes to the families that do build.
+ print(f'ci_set_matrix: UNSCOPED - no selected family is built by any '
+ f'toolchain here ({", ".join(unbuilt)}), emitting the full matrix',
+ file=sys.stderr)
+ return set_matrix_json(None)
+ if unbuilt:
+ print(f'ci_set_matrix: selected families built by no toolchain here: '
+ f'{", ".join(unbuilt)}', file=sys.stderr)
+ print(json.dumps(matrix))
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ group = parser.add_mutually_exclusive_group()
+ group.add_argument('--select', help='tools/ci_select.py JSON; scopes families when build.full is false')
+ # a whole selection as one argv/env value can exceed the exec limits on a big
+ # diff, which fails the calling step BEFORE it can fall open; callers that
+ # already have the selection on disk pass the path instead
+ group.add_argument('--select-file', help='file holding the same JSON as --select')
+ group.add_argument('--base', help='git ref: run tools/ci_select.py --base REF and scope from it')
+ args = parser.parse_args()
+
+ select = None
+ try:
+ if args.select:
+ select = json.loads(args.select)
+ elif args.select_file:
+ with open(args.select_file) as f:
+ select = json.load(f)
+ elif args.base:
+ root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+ r = subprocess.run([sys.executable, os.path.join(root, 'tools', 'ci_select.py'),
+ '--base', args.base],
+ capture_output=True, text=True, cwd=root, check=True)
+ select = json.loads(r.stdout)
+ except Exception as e: # fail-open: an unusable selection must never turn into a red job
+ # UNSCOPED is the marker build.yml greps for: it must then drop the build extras
+ # (example map, family regex) too, or a full build gets labelled and filtered as
+ # a scoped one. Keep the token on every fall-open path.
+ print(f'ci_set_matrix: UNSCOPED - selection unusable ({e}), emitting the full '
+ f'matrix', file=sys.stderr)
+ select = None
+ set_matrix_json(select)
+
+
+if __name__ == '__main__':
+ main()