1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
|
#!/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.
unbuilt = sorted(f for f in sel_fams if f not in family_list)
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()
|