blob: 5321f31b0bc328aa49087956f1cc5eca6511762a [file] [log] [blame]
Martí Bolívar8bd6d082020-12-09 12:03:19 -08001#!/usr/bin/env python3
2
3# Copyright (c) 2020 Nordic Semiconductor ASA
4# SPDX-License-Identifier: Apache-2.0
5
6import argparse
7from collections import defaultdict
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +02008from dataclasses import dataclass, field
Martí Bolívar8bd6d082020-12-09 12:03:19 -08009import itertools
10from pathlib import Path
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +020011import pykwalify.core
12import sys
13from typing import List
14import yaml
15import list_hardware
16
17BOARD_SCHEMA_PATH = str(Path(__file__).parent / 'schemas' / 'board-schema.yml')
18with open(BOARD_SCHEMA_PATH, 'r') as f:
19 board_schema = yaml.safe_load(f.read())
20
21BOARD_YML = 'board.yml'
Martí Bolívar8bd6d082020-12-09 12:03:19 -080022
Martí Bolívar8bd6d082020-12-09 12:03:19 -080023#
24# This is shared code between the build system's 'boards' target
25# and the 'west boards' extension command. If you change it, make
26# sure to test both ways it can be used.
27#
28# (It's done this way to keep west optional, making it possible to run
29# 'ninja boards' in a build directory without west installed.)
30#
31
Torsten Rasmussen732c5042024-03-19 15:26:59 +010032
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +020033@dataclass
34class Revision:
Martí Bolívar8bd6d082020-12-09 12:03:19 -080035 name: str
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +020036 variants: List[str] = field(default_factory=list)
37
38 @staticmethod
39 def from_dict(revision):
40 revisions = []
41 for r in revision.get('revisions', []):
42 revisions.append(Revision.from_dict(r))
43 return Revision(revision['name'], revisions)
44
45
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +020046@dataclass
47class Variant:
48 name: str
49 variants: List[str] = field(default_factory=list)
50
51 @staticmethod
52 def from_dict(variant):
53 variants = []
54 for v in variant.get('variants', []):
55 variants.append(Variant.from_dict(v))
56 return Variant(variant['name'], variants)
57
58
59@dataclass
60class Cpucluster:
61 name: str
62 variants: List[str] = field(default_factory=list)
63
64
65@dataclass
66class Soc:
67 name: str
68 cpuclusters: List[str] = field(default_factory=list)
69 variants: List[str] = field(default_factory=list)
70
71 @staticmethod
72 def from_soc(soc, variants):
73 if soc is None:
74 return None
75 if soc.cpuclusters:
76 cpus = []
77 for c in soc.cpuclusters:
78 cpus.append(Cpucluster(c,
79 [Variant.from_dict(v) for v in variants if c == v['cpucluster']]
80 ))
81 return Soc(soc.name, cpuclusters=cpus)
82 return Soc(soc.name, variants=[Variant.from_dict(v) for v in variants])
83
84
85@dataclass(frozen=True)
86class Board:
87 name: str
Martí Bolívar8bd6d082020-12-09 12:03:19 -080088 dir: Path
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +020089 hwm: str
90 arch: str = None
91 vendor: str = None
92 revision_format: str = None
93 revision_default: str = None
94 revision_exact: bool = False
95 revisions: List[str] = field(default_factory=list, compare=False)
96 socs: List[Soc] = field(default_factory=list, compare=False)
97 variants: List[str] = field(default_factory=list, compare=False)
98
Martí Bolívar8bd6d082020-12-09 12:03:19 -080099
100def board_key(board):
101 return board.name
102
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100103
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800104def find_arch2boards(args):
105 arch2board_set = find_arch2board_set(args)
106 return {arch: sorted(arch2board_set[arch], key=board_key)
107 for arch in arch2board_set}
108
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100109
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800110def find_boards(args):
111 return sorted(itertools.chain(*find_arch2board_set(args).values()),
112 key=board_key)
113
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100114
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800115def find_arch2board_set(args):
116 arches = sorted(find_arches(args))
117 ret = defaultdict(set)
118
Torsten Rasmussenc3620c82022-08-05 09:54:47 +0200119 for root in args.board_roots:
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200120 for arch, boards in find_arch2board_set_in(root, arches, args.board_dir).items():
121 if args.board is not None:
122 ret[arch] |= {b for b in boards if b.name == args.board}
123 else:
124 ret[arch] |= boards
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800125
126 return ret
127
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100128
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800129def find_arches(args):
Torsten Rasmussenc3620c82022-08-05 09:54:47 +0200130 arch_set = set()
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800131
132 for root in args.arch_roots:
133 arch_set |= find_arches_in(root)
134
135 return arch_set
136
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100137
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800138def find_arches_in(root):
139 ret = set()
140 arch = root / 'arch'
141 common = arch / 'common'
142
143 if not arch.is_dir():
144 return ret
145
146 for maybe_arch in arch.iterdir():
147 if not maybe_arch.is_dir() or maybe_arch == common:
148 continue
149 ret.add(maybe_arch.name)
150
151 return ret
152
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100153
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200154def find_arch2board_set_in(root, arches, board_dir):
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800155 ret = defaultdict(set)
156 boards = root / 'boards'
157
158 for arch in arches:
Torsten Rasmussenfd772f82024-03-05 09:29:48 +0100159 if not (boards / arch).is_dir():
Marcin Niestroj35c882d2021-05-08 15:36:19 +0200160 continue
161
Torsten Rasmussenfd772f82024-03-05 09:29:48 +0100162 for maybe_board in (boards / arch).iterdir():
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800163 if not maybe_board.is_dir():
164 continue
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200165 if board_dir is not None and board_dir != maybe_board:
166 continue
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800167 for maybe_defconfig in maybe_board.iterdir():
168 file_name = maybe_defconfig.name
Torsten Rasmussenfd772f82024-03-05 09:29:48 +0100169 if file_name.endswith('_defconfig') and not (maybe_board / BOARD_YML).is_file():
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800170 board_name = file_name[:-len('_defconfig')]
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200171 ret[arch].add(Board(board_name, maybe_board, 'v1', arch=arch))
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800172
173 return ret
174
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200175
176def load_v2_boards(board_name, board_yml, systems):
177 boards = []
178 if board_yml.is_file():
179 with board_yml.open('r') as f:
180 b = yaml.safe_load(f.read())
181
182 try:
183 pykwalify.core.Core(source_data=b, schema_data=board_schema).validate()
184 except pykwalify.errors.SchemaError as e:
185 sys.exit('ERROR: Malformed "build" section in file: {}\n{}'
186 .format(board_yml.as_posix(), e))
187
188 mutual_exclusive = {'board', 'boards'}
189 if len(mutual_exclusive - b.keys()) < 1:
190 sys.exit(f'ERROR: Malformed content in file: {board_yml.as_posix()}\n'
191 f'{mutual_exclusive} are mutual exclusive at this level.')
192
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100193 board_array = b.get('boards', [b.get('board', None)])
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200194 for board in board_array:
195 if board_name is not None:
196 if board['name'] != board_name:
197 # Not the board we're looking for, ignore.
198 continue
199
200 board_revision = board.get('revision')
201 if board_revision is not None and board_revision.get('format') != 'custom':
202 if board_revision.get('default') is None:
203 sys.exit(f'ERROR: Malformed "board" section in file: {board_yml.as_posix()}\n'
204 "Cannot find required key 'default'. Path: '/board/revision.'")
205 if board_revision.get('revisions') is None:
206 sys.exit(f'ERROR: Malformed "board" section in file: {board_yml.as_posix()}\n'
207 "Cannot find required key 'revisions'. Path: '/board/revision.'")
208
209 mutual_exclusive = {'socs', 'variants'}
210 if len(mutual_exclusive - board.keys()) < 1:
211 sys.exit(f'ERROR: Malformed "board" section in file: {board_yml.as_posix()}\n'
212 f'{mutual_exclusive} are mutual exclusive at this level.')
213 socs = [Soc.from_soc(systems.get_soc(s['name']), s.get('variants', []))
214 for s in board.get('socs', {})]
215
216 board = Board(
217 name=board['name'],
218 dir=board_yml.parent,
219 vendor=board.get('vendor'),
220 revision_format=board.get('revision', {}).get('format'),
221 revision_default=board.get('revision', {}).get('default'),
222 revision_exact=board.get('revision', {}).get('exact', False),
223 revisions=[Revision.from_dict(v) for v in
224 board.get('revision', {}).get('revisions', [])],
225 socs=socs,
226 variants=[Variant.from_dict(v) for v in board.get('variants', [])],
227 hwm='v2',
228 )
229 boards.append(board)
230 return boards
231
232
233def find_v2_boards(args):
234 root_args = argparse.Namespace(**{'soc_roots': args.soc_roots})
235 systems = list_hardware.find_v2_systems(root_args)
236
237 boards = []
238 board_files = []
239 for root in args.board_roots:
240 board_files.extend((root / 'boards').rglob(BOARD_YML))
241
242 for board_yml in board_files:
243 b = load_v2_boards(args.board, board_yml, systems)
244 boards.extend(b)
245 return boards
246
247
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800248def parse_args():
Jamie McCraeec704442023-01-04 16:08:36 +0000249 parser = argparse.ArgumentParser(allow_abbrev=False)
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800250 add_args(parser)
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200251 add_args_formatting(parser)
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800252 return parser.parse_args()
253
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200254
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800255def add_args(parser):
256 # Remember to update west-completion.bash if you add or remove
257 # flags
258 parser.add_argument("--arch-root", dest='arch_roots', default=[],
259 type=Path, action='append',
Torsten Rasmussenc3620c82022-08-05 09:54:47 +0200260 help='add an architecture root, may be given more than once')
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800261 parser.add_argument("--board-root", dest='board_roots', default=[],
262 type=Path, action='append',
Torsten Rasmussenc3620c82022-08-05 09:54:47 +0200263 help='add a board root, may be given more than once')
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200264 parser.add_argument("--soc-root", dest='soc_roots', default=[],
265 type=Path, action='append',
266 help='add a soc root, may be given more than once')
267 parser.add_argument("--board", dest='board', default=None,
268 help='lookup the specific board, fail if not found')
269 parser.add_argument("--board-dir", default=None, type=Path,
270 help='Only look for boards at the specific location')
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800271
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200272
273def add_args_formatting(parser):
274 parser.add_argument("--cmakeformat", default=None,
275 help='''CMake Format string to use to list each board''')
276
277
Torsten Rasmussen88d8b232024-03-07 10:06:34 +0100278def variant_v2_qualifiers(variant, qualifiers = None):
279 qualifiers_list = [variant.name] if qualifiers is None else [qualifiers + '/' + variant.name]
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200280 for v in variant.variants:
Torsten Rasmussen88d8b232024-03-07 10:06:34 +0100281 qualifiers_list.extend(variant_v2_qualifiers(v, qualifiers_list[0]))
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100282 return qualifiers_list
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200283
284
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100285def board_v2_qualifiers(board):
286 qualifiers_list = []
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200287
288 for s in board.socs:
289 if s.cpuclusters:
290 for c in s.cpuclusters:
Torsten Rasmussen88d8b232024-03-07 10:06:34 +0100291 id_str = s.name + '/' + c.name
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100292 qualifiers_list.append(id_str)
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200293 for v in c.variants:
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100294 qualifiers_list.extend(variant_v2_qualifiers(v, id_str))
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200295 else:
Torsten Rasmussen88d8b232024-03-07 10:06:34 +0100296 qualifiers_list.append(s.name)
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200297 for v in s.variants:
Torsten Rasmussen88d8b232024-03-07 10:06:34 +0100298 qualifiers_list.extend(variant_v2_qualifiers(v, s.name))
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200299
300 for v in board.variants:
Torsten Rasmussen88d8b232024-03-07 10:06:34 +0100301 qualifiers_list.extend(variant_v2_qualifiers(v))
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100302 return qualifiers_list
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200303
304
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100305def board_v2_qualifiers_csv(board):
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200306 # Return in csv (comma separated value) format
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100307 return ",".join(board_v2_qualifiers(board))
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200308
309
310def dump_v2_boards(args):
311 if args.board_dir:
312 root_args = argparse.Namespace(**{'soc_roots': args.soc_roots})
313 systems = list_hardware.find_v2_systems(root_args)
314 boards = load_v2_boards(args.board, args.board_dir / BOARD_YML, systems)
315 else:
316 boards = find_v2_boards(args)
317
318 for b in boards:
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100319 qualifiers_list = board_v2_qualifiers(b)
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200320 if args.cmakeformat is not None:
321 notfound = lambda x: x or 'NOTFOUND'
322 info = args.cmakeformat.format(
323 NAME='NAME;' + b.name,
324 DIR='DIR;' + str(b.dir.as_posix()),
325 VENDOR='VENDOR;' + notfound(b.vendor),
326 HWM='HWM;' + b.hwm,
327 REVISION_DEFAULT='REVISION_DEFAULT;' + notfound(b.revision_default),
328 REVISION_FORMAT='REVISION_FORMAT;' + notfound(b.revision_format),
329 REVISION_EXACT='REVISION_EXACT;' + str(b.revision_exact),
330 REVISIONS='REVISIONS;' + ';'.join(
331 [x.name for x in b.revisions]),
332 SOCS='SOCS;' + ';'.join([s.name for s in b.socs]),
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100333 QUALIFIERS='QUALIFIERS;' + ';'.join(qualifiers_list)
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200334 )
335 print(info)
336 else:
337 print(f'{b.name}')
338
339
340def dump_boards(args):
341 arch2boards = find_arch2boards(args)
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800342 for arch, boards in arch2boards.items():
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200343 if args.cmakeformat is None:
344 print(f'{arch}:')
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800345 for board in boards:
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200346 if args.cmakeformat is not None:
347 info = args.cmakeformat.format(
348 NAME='NAME;' + board.name,
349 DIR='DIR;' + str(board.dir.as_posix()),
350 HWM='HWM;' + board.hwm,
351 VENDOR='VENDOR;NOTFOUND',
352 REVISION_DEFAULT='REVISION_DEFAULT;NOTFOUND',
353 REVISION_FORMAT='REVISION_FORMAT;NOTFOUND',
354 REVISION_EXACT='REVISION_EXACT;NOTFOUND',
355 REVISIONS='REVISIONS;NOTFOUND',
356 VARIANT_DEFAULT='VARIANT_DEFAULT;NOTFOUND',
357 SOCS='SOCS;',
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100358 QUALIFIERS='QUALIFIERS;'
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200359 )
360 print(info)
361 else:
362 print(f' {board.name}')
363
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800364
365if __name__ == '__main__':
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200366 args = parse_args()
367 dump_boards(args)
368 dump_v2_boards(args)