blob: 597ecf91809b72f868f30e2938ba5166445ab2a9 [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
Grzegorz Swiderski9dabce42024-03-22 20:17:03 +010016from list_hardware import unique_paths
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +020017
18BOARD_SCHEMA_PATH = str(Path(__file__).parent / 'schemas' / 'board-schema.yml')
19with open(BOARD_SCHEMA_PATH, 'r') as f:
20 board_schema = yaml.safe_load(f.read())
21
22BOARD_YML = 'board.yml'
Martí Bolívar8bd6d082020-12-09 12:03:19 -080023
Martí Bolívar8bd6d082020-12-09 12:03:19 -080024#
25# This is shared code between the build system's 'boards' target
26# and the 'west boards' extension command. If you change it, make
27# sure to test both ways it can be used.
28#
29# (It's done this way to keep west optional, making it possible to run
30# 'ninja boards' in a build directory without west installed.)
31#
32
Torsten Rasmussen732c5042024-03-19 15:26:59 +010033
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +020034@dataclass
35class Revision:
Martí Bolívar8bd6d082020-12-09 12:03:19 -080036 name: str
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +020037 variants: List[str] = field(default_factory=list)
38
39 @staticmethod
40 def from_dict(revision):
41 revisions = []
42 for r in revision.get('revisions', []):
43 revisions.append(Revision.from_dict(r))
44 return Revision(revision['name'], revisions)
45
46
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +020047@dataclass
48class Variant:
49 name: str
50 variants: List[str] = field(default_factory=list)
51
52 @staticmethod
53 def from_dict(variant):
54 variants = []
55 for v in variant.get('variants', []):
56 variants.append(Variant.from_dict(v))
57 return Variant(variant['name'], variants)
58
59
60@dataclass
61class Cpucluster:
62 name: str
63 variants: List[str] = field(default_factory=list)
64
65
66@dataclass
67class Soc:
68 name: str
69 cpuclusters: List[str] = field(default_factory=list)
70 variants: List[str] = field(default_factory=list)
71
72 @staticmethod
73 def from_soc(soc, variants):
74 if soc is None:
75 return None
76 if soc.cpuclusters:
77 cpus = []
78 for c in soc.cpuclusters:
79 cpus.append(Cpucluster(c,
80 [Variant.from_dict(v) for v in variants if c == v['cpucluster']]
81 ))
82 return Soc(soc.name, cpuclusters=cpus)
83 return Soc(soc.name, variants=[Variant.from_dict(v) for v in variants])
84
85
86@dataclass(frozen=True)
87class Board:
88 name: str
Martí Bolívar8bd6d082020-12-09 12:03:19 -080089 dir: Path
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +020090 hwm: str
91 arch: str = None
92 vendor: str = None
93 revision_format: str = None
94 revision_default: str = None
95 revision_exact: bool = False
96 revisions: List[str] = field(default_factory=list, compare=False)
97 socs: List[Soc] = field(default_factory=list, compare=False)
98 variants: List[str] = field(default_factory=list, compare=False)
99
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800100
101def board_key(board):
102 return board.name
103
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100104
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800105def find_arch2boards(args):
106 arch2board_set = find_arch2board_set(args)
107 return {arch: sorted(arch2board_set[arch], key=board_key)
108 for arch in arch2board_set}
109
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100110
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800111def find_boards(args):
112 return sorted(itertools.chain(*find_arch2board_set(args).values()),
113 key=board_key)
114
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100115
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800116def find_arch2board_set(args):
117 arches = sorted(find_arches(args))
118 ret = defaultdict(set)
119
Grzegorz Swiderski9dabce42024-03-22 20:17:03 +0100120 for root in unique_paths(args.board_roots):
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200121 for arch, boards in find_arch2board_set_in(root, arches, args.board_dir).items():
122 if args.board is not None:
123 ret[arch] |= {b for b in boards if b.name == args.board}
124 else:
125 ret[arch] |= boards
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800126
127 return ret
128
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100129
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800130def find_arches(args):
Torsten Rasmussenc3620c82022-08-05 09:54:47 +0200131 arch_set = set()
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800132
Grzegorz Swiderski9dabce42024-03-22 20:17:03 +0100133 for root in unique_paths(args.arch_roots):
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800134 arch_set |= find_arches_in(root)
135
136 return arch_set
137
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100138
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800139def find_arches_in(root):
140 ret = set()
141 arch = root / 'arch'
142 common = arch / 'common'
143
144 if not arch.is_dir():
145 return ret
146
147 for maybe_arch in arch.iterdir():
148 if not maybe_arch.is_dir() or maybe_arch == common:
149 continue
150 ret.add(maybe_arch.name)
151
152 return ret
153
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100154
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200155def find_arch2board_set_in(root, arches, board_dir):
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800156 ret = defaultdict(set)
157 boards = root / 'boards'
158
159 for arch in arches:
Torsten Rasmussenfd772f82024-03-05 09:29:48 +0100160 if not (boards / arch).is_dir():
Marcin Niestroj35c882d2021-05-08 15:36:19 +0200161 continue
162
Torsten Rasmussenfd772f82024-03-05 09:29:48 +0100163 for maybe_board in (boards / arch).iterdir():
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800164 if not maybe_board.is_dir():
165 continue
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200166 if board_dir is not None and board_dir != maybe_board:
167 continue
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800168 for maybe_defconfig in maybe_board.iterdir():
169 file_name = maybe_defconfig.name
Torsten Rasmussenfd772f82024-03-05 09:29:48 +0100170 if file_name.endswith('_defconfig') and not (maybe_board / BOARD_YML).is_file():
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800171 board_name = file_name[:-len('_defconfig')]
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200172 ret[arch].add(Board(board_name, maybe_board, 'v1', arch=arch))
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800173
174 return ret
175
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200176
177def load_v2_boards(board_name, board_yml, systems):
178 boards = []
179 if board_yml.is_file():
180 with board_yml.open('r') as f:
181 b = yaml.safe_load(f.read())
182
183 try:
184 pykwalify.core.Core(source_data=b, schema_data=board_schema).validate()
185 except pykwalify.errors.SchemaError as e:
186 sys.exit('ERROR: Malformed "build" section in file: {}\n{}'
187 .format(board_yml.as_posix(), e))
188
189 mutual_exclusive = {'board', 'boards'}
190 if len(mutual_exclusive - b.keys()) < 1:
191 sys.exit(f'ERROR: Malformed content in file: {board_yml.as_posix()}\n'
192 f'{mutual_exclusive} are mutual exclusive at this level.')
193
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100194 board_array = b.get('boards', [b.get('board', None)])
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200195 for board in board_array:
196 if board_name is not None:
197 if board['name'] != board_name:
198 # Not the board we're looking for, ignore.
199 continue
200
201 board_revision = board.get('revision')
202 if board_revision is not None and board_revision.get('format') != 'custom':
203 if board_revision.get('default') is None:
204 sys.exit(f'ERROR: Malformed "board" section in file: {board_yml.as_posix()}\n'
205 "Cannot find required key 'default'. Path: '/board/revision.'")
206 if board_revision.get('revisions') is None:
207 sys.exit(f'ERROR: Malformed "board" section in file: {board_yml.as_posix()}\n'
208 "Cannot find required key 'revisions'. Path: '/board/revision.'")
209
210 mutual_exclusive = {'socs', 'variants'}
211 if len(mutual_exclusive - board.keys()) < 1:
212 sys.exit(f'ERROR: Malformed "board" section in file: {board_yml.as_posix()}\n'
213 f'{mutual_exclusive} are mutual exclusive at this level.')
214 socs = [Soc.from_soc(systems.get_soc(s['name']), s.get('variants', []))
215 for s in board.get('socs', {})]
216
217 board = Board(
218 name=board['name'],
219 dir=board_yml.parent,
220 vendor=board.get('vendor'),
221 revision_format=board.get('revision', {}).get('format'),
222 revision_default=board.get('revision', {}).get('default'),
223 revision_exact=board.get('revision', {}).get('exact', False),
224 revisions=[Revision.from_dict(v) for v in
225 board.get('revision', {}).get('revisions', [])],
226 socs=socs,
227 variants=[Variant.from_dict(v) for v in board.get('variants', [])],
228 hwm='v2',
229 )
230 boards.append(board)
231 return boards
232
233
234def find_v2_boards(args):
235 root_args = argparse.Namespace(**{'soc_roots': args.soc_roots})
236 systems = list_hardware.find_v2_systems(root_args)
237
238 boards = []
239 board_files = []
Grzegorz Swiderski9dabce42024-03-22 20:17:03 +0100240 for root in unique_paths(args.board_roots):
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200241 board_files.extend((root / 'boards').rglob(BOARD_YML))
242
243 for board_yml in board_files:
244 b = load_v2_boards(args.board, board_yml, systems)
245 boards.extend(b)
246 return boards
247
248
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800249def parse_args():
Jamie McCraeec704442023-01-04 16:08:36 +0000250 parser = argparse.ArgumentParser(allow_abbrev=False)
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800251 add_args(parser)
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200252 add_args_formatting(parser)
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800253 return parser.parse_args()
254
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200255
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800256def add_args(parser):
257 # Remember to update west-completion.bash if you add or remove
258 # flags
259 parser.add_argument("--arch-root", dest='arch_roots', default=[],
260 type=Path, action='append',
Torsten Rasmussenc3620c82022-08-05 09:54:47 +0200261 help='add an architecture root, may be given more than once')
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800262 parser.add_argument("--board-root", dest='board_roots', default=[],
263 type=Path, action='append',
Torsten Rasmussenc3620c82022-08-05 09:54:47 +0200264 help='add a board root, may be given more than once')
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200265 parser.add_argument("--soc-root", dest='soc_roots', default=[],
266 type=Path, action='append',
267 help='add a soc root, may be given more than once')
268 parser.add_argument("--board", dest='board', default=None,
269 help='lookup the specific board, fail if not found')
270 parser.add_argument("--board-dir", default=None, type=Path,
271 help='Only look for boards at the specific location')
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800272
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200273
274def add_args_formatting(parser):
275 parser.add_argument("--cmakeformat", default=None,
276 help='''CMake Format string to use to list each board''')
277
278
Torsten Rasmussen88d8b232024-03-07 10:06:34 +0100279def variant_v2_qualifiers(variant, qualifiers = None):
280 qualifiers_list = [variant.name] if qualifiers is None else [qualifiers + '/' + variant.name]
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200281 for v in variant.variants:
Torsten Rasmussen88d8b232024-03-07 10:06:34 +0100282 qualifiers_list.extend(variant_v2_qualifiers(v, qualifiers_list[0]))
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100283 return qualifiers_list
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200284
285
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100286def board_v2_qualifiers(board):
287 qualifiers_list = []
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200288
289 for s in board.socs:
290 if s.cpuclusters:
291 for c in s.cpuclusters:
Torsten Rasmussen88d8b232024-03-07 10:06:34 +0100292 id_str = s.name + '/' + c.name
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100293 qualifiers_list.append(id_str)
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200294 for v in c.variants:
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100295 qualifiers_list.extend(variant_v2_qualifiers(v, id_str))
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200296 else:
Torsten Rasmussen88d8b232024-03-07 10:06:34 +0100297 qualifiers_list.append(s.name)
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200298 for v in s.variants:
Torsten Rasmussen88d8b232024-03-07 10:06:34 +0100299 qualifiers_list.extend(variant_v2_qualifiers(v, s.name))
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200300
301 for v in board.variants:
Torsten Rasmussen88d8b232024-03-07 10:06:34 +0100302 qualifiers_list.extend(variant_v2_qualifiers(v))
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100303 return qualifiers_list
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200304
305
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100306def board_v2_qualifiers_csv(board):
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200307 # Return in csv (comma separated value) format
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100308 return ",".join(board_v2_qualifiers(board))
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200309
310
311def dump_v2_boards(args):
312 if args.board_dir:
313 root_args = argparse.Namespace(**{'soc_roots': args.soc_roots})
314 systems = list_hardware.find_v2_systems(root_args)
315 boards = load_v2_boards(args.board, args.board_dir / BOARD_YML, systems)
316 else:
317 boards = find_v2_boards(args)
318
319 for b in boards:
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100320 qualifiers_list = board_v2_qualifiers(b)
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200321 if args.cmakeformat is not None:
322 notfound = lambda x: x or 'NOTFOUND'
323 info = args.cmakeformat.format(
324 NAME='NAME;' + b.name,
325 DIR='DIR;' + str(b.dir.as_posix()),
326 VENDOR='VENDOR;' + notfound(b.vendor),
327 HWM='HWM;' + b.hwm,
328 REVISION_DEFAULT='REVISION_DEFAULT;' + notfound(b.revision_default),
329 REVISION_FORMAT='REVISION_FORMAT;' + notfound(b.revision_format),
330 REVISION_EXACT='REVISION_EXACT;' + str(b.revision_exact),
331 REVISIONS='REVISIONS;' + ';'.join(
332 [x.name for x in b.revisions]),
333 SOCS='SOCS;' + ';'.join([s.name for s in b.socs]),
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100334 QUALIFIERS='QUALIFIERS;' + ';'.join(qualifiers_list)
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200335 )
336 print(info)
337 else:
338 print(f'{b.name}')
339
340
341def dump_boards(args):
342 arch2boards = find_arch2boards(args)
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800343 for arch, boards in arch2boards.items():
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200344 if args.cmakeformat is None:
345 print(f'{arch}:')
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800346 for board in boards:
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200347 if args.cmakeformat is not None:
348 info = args.cmakeformat.format(
349 NAME='NAME;' + board.name,
350 DIR='DIR;' + str(board.dir.as_posix()),
351 HWM='HWM;' + board.hwm,
352 VENDOR='VENDOR;NOTFOUND',
353 REVISION_DEFAULT='REVISION_DEFAULT;NOTFOUND',
354 REVISION_FORMAT='REVISION_FORMAT;NOTFOUND',
355 REVISION_EXACT='REVISION_EXACT;NOTFOUND',
356 REVISIONS='REVISIONS;NOTFOUND',
357 VARIANT_DEFAULT='VARIANT_DEFAULT;NOTFOUND',
358 SOCS='SOCS;',
Torsten Rasmussen732c5042024-03-19 15:26:59 +0100359 QUALIFIERS='QUALIFIERS;'
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200360 )
361 print(info)
362 else:
363 print(f' {board.name}')
364
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800365
366if __name__ == '__main__':
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200367 args = parse_args()
368 dump_boards(args)
369 dump_v2_boards(args)