blob: 55fbe970d745d56fc4b0ca590a3bbcec27bda243 [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 Rasmussen8dc3f852022-09-14 22:23:15 +020032@dataclass
33class Revision:
Martí Bolívar8bd6d082020-12-09 12:03:19 -080034 name: str
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +020035 variants: List[str] = field(default_factory=list)
36
37 @staticmethod
38 def from_dict(revision):
39 revisions = []
40 for r in revision.get('revisions', []):
41 revisions.append(Revision.from_dict(r))
42 return Revision(revision['name'], revisions)
43
44
45
46@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
103def find_arch2boards(args):
104 arch2board_set = find_arch2board_set(args)
105 return {arch: sorted(arch2board_set[arch], key=board_key)
106 for arch in arch2board_set}
107
108def find_boards(args):
109 return sorted(itertools.chain(*find_arch2board_set(args).values()),
110 key=board_key)
111
112def find_arch2board_set(args):
113 arches = sorted(find_arches(args))
114 ret = defaultdict(set)
115
Torsten Rasmussenc3620c82022-08-05 09:54:47 +0200116 for root in args.board_roots:
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200117 for arch, boards in find_arch2board_set_in(root, arches, args.board_dir).items():
118 if args.board is not None:
119 ret[arch] |= {b for b in boards if b.name == args.board}
120 else:
121 ret[arch] |= boards
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800122
123 return ret
124
125def find_arches(args):
Torsten Rasmussenc3620c82022-08-05 09:54:47 +0200126 arch_set = set()
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800127
128 for root in args.arch_roots:
129 arch_set |= find_arches_in(root)
130
131 return arch_set
132
133def find_arches_in(root):
134 ret = set()
135 arch = root / 'arch'
136 common = arch / 'common'
137
138 if not arch.is_dir():
139 return ret
140
141 for maybe_arch in arch.iterdir():
142 if not maybe_arch.is_dir() or maybe_arch == common:
143 continue
144 ret.add(maybe_arch.name)
145
146 return ret
147
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200148def find_arch2board_set_in(root, arches, board_dir):
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800149 ret = defaultdict(set)
150 boards = root / 'boards'
151
152 for arch in arches:
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200153 if not (boards / "boards_legacy" / arch).is_dir():
Marcin Niestroj35c882d2021-05-08 15:36:19 +0200154 continue
155
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200156 for maybe_board in (boards / "boards_legacy" / arch).iterdir():
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800157 if not maybe_board.is_dir():
158 continue
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200159 if board_dir is not None and board_dir != maybe_board:
160 continue
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800161 for maybe_defconfig in maybe_board.iterdir():
162 file_name = maybe_defconfig.name
163 if file_name.endswith('_defconfig'):
164 board_name = file_name[:-len('_defconfig')]
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200165 ret[arch].add(Board(board_name, maybe_board, 'v1', arch=arch))
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800166
167 return ret
168
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200169
170def load_v2_boards(board_name, board_yml, systems):
171 boards = []
172 if board_yml.is_file():
173 with board_yml.open('r') as f:
174 b = yaml.safe_load(f.read())
175
176 try:
177 pykwalify.core.Core(source_data=b, schema_data=board_schema).validate()
178 except pykwalify.errors.SchemaError as e:
179 sys.exit('ERROR: Malformed "build" section in file: {}\n{}'
180 .format(board_yml.as_posix(), e))
181
182 mutual_exclusive = {'board', 'boards'}
183 if len(mutual_exclusive - b.keys()) < 1:
184 sys.exit(f'ERROR: Malformed content in file: {board_yml.as_posix()}\n'
185 f'{mutual_exclusive} are mutual exclusive at this level.')
186
187 board_array = b.get('boards', [ b.get('board', None) ])
188 for board in board_array:
189 if board_name is not None:
190 if board['name'] != board_name:
191 # Not the board we're looking for, ignore.
192 continue
193
194 board_revision = board.get('revision')
195 if board_revision is not None and board_revision.get('format') != 'custom':
196 if board_revision.get('default') is None:
197 sys.exit(f'ERROR: Malformed "board" section in file: {board_yml.as_posix()}\n'
198 "Cannot find required key 'default'. Path: '/board/revision.'")
199 if board_revision.get('revisions') is None:
200 sys.exit(f'ERROR: Malformed "board" section in file: {board_yml.as_posix()}\n'
201 "Cannot find required key 'revisions'. Path: '/board/revision.'")
202
203 mutual_exclusive = {'socs', 'variants'}
204 if len(mutual_exclusive - board.keys()) < 1:
205 sys.exit(f'ERROR: Malformed "board" section in file: {board_yml.as_posix()}\n'
206 f'{mutual_exclusive} are mutual exclusive at this level.')
207 socs = [Soc.from_soc(systems.get_soc(s['name']), s.get('variants', []))
208 for s in board.get('socs', {})]
209
210 board = Board(
211 name=board['name'],
212 dir=board_yml.parent,
213 vendor=board.get('vendor'),
214 revision_format=board.get('revision', {}).get('format'),
215 revision_default=board.get('revision', {}).get('default'),
216 revision_exact=board.get('revision', {}).get('exact', False),
217 revisions=[Revision.from_dict(v) for v in
218 board.get('revision', {}).get('revisions', [])],
219 socs=socs,
220 variants=[Variant.from_dict(v) for v in board.get('variants', [])],
221 hwm='v2',
222 )
223 boards.append(board)
224 return boards
225
226
227def find_v2_boards(args):
228 root_args = argparse.Namespace(**{'soc_roots': args.soc_roots})
229 systems = list_hardware.find_v2_systems(root_args)
230
231 boards = []
232 board_files = []
233 for root in args.board_roots:
234 board_files.extend((root / 'boards').rglob(BOARD_YML))
235
236 for board_yml in board_files:
237 b = load_v2_boards(args.board, board_yml, systems)
238 boards.extend(b)
239 return boards
240
241
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800242def parse_args():
Jamie McCraeec704442023-01-04 16:08:36 +0000243 parser = argparse.ArgumentParser(allow_abbrev=False)
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800244 add_args(parser)
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200245 add_args_formatting(parser)
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800246 return parser.parse_args()
247
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200248
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800249def add_args(parser):
250 # Remember to update west-completion.bash if you add or remove
251 # flags
252 parser.add_argument("--arch-root", dest='arch_roots', default=[],
253 type=Path, action='append',
Torsten Rasmussenc3620c82022-08-05 09:54:47 +0200254 help='add an architecture root, may be given more than once')
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800255 parser.add_argument("--board-root", dest='board_roots', default=[],
256 type=Path, action='append',
Torsten Rasmussenc3620c82022-08-05 09:54:47 +0200257 help='add a board root, may be given more than once')
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200258 parser.add_argument("--soc-root", dest='soc_roots', default=[],
259 type=Path, action='append',
260 help='add a soc root, may be given more than once')
261 parser.add_argument("--board", dest='board', default=None,
262 help='lookup the specific board, fail if not found')
263 parser.add_argument("--board-dir", default=None, type=Path,
264 help='Only look for boards at the specific location')
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800265
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200266
267def add_args_formatting(parser):
268 parser.add_argument("--cmakeformat", default=None,
269 help='''CMake Format string to use to list each board''')
270
271
272def variant_v2_identifiers(variant, identifier):
273 identifiers = [identifier + '/' + variant.name]
274 for v in variant.variants:
275 identifiers.extend(variant_v2_identifiers(v, identifier + '/' + variant.name))
276 return identifiers
277
278
279def board_v2_identifiers(board):
280 identifiers = []
281
282 for s in board.socs:
283 if s.cpuclusters:
284 for c in s.cpuclusters:
285 id_str = board.name + '/' + s.name + '/' + c.name
286 identifiers.append(id_str)
287 for v in c.variants:
288 identifiers.extend(variant_v2_identifiers(v, id_str))
289 else:
290 id_str = board.name + '/' + s.name
291 identifiers.append(id_str)
292 for v in s.variants:
293 identifiers.extend(variant_v2_identifiers(v, id_str))
294
295 if not board.socs:
296 identifiers.append(board.name)
297
298 for v in board.variants:
299 identifiers.extend(variant_v2_identifiers(v, board.name))
300 return identifiers
301
302
303def board_v2_identifiers_csv(board):
304 # Return in csv (comma separated value) format
305 return ",".join(board_v2_identifiers(board))
306
307
308def dump_v2_boards(args):
309 if args.board_dir:
310 root_args = argparse.Namespace(**{'soc_roots': args.soc_roots})
311 systems = list_hardware.find_v2_systems(root_args)
312 boards = load_v2_boards(args.board, args.board_dir / BOARD_YML, systems)
313 else:
314 boards = find_v2_boards(args)
315
316 for b in boards:
317 identifiers = board_v2_identifiers(b)
318 if args.cmakeformat is not None:
319 notfound = lambda x: x or 'NOTFOUND'
320 info = args.cmakeformat.format(
321 NAME='NAME;' + b.name,
322 DIR='DIR;' + str(b.dir.as_posix()),
323 VENDOR='VENDOR;' + notfound(b.vendor),
324 HWM='HWM;' + b.hwm,
325 REVISION_DEFAULT='REVISION_DEFAULT;' + notfound(b.revision_default),
326 REVISION_FORMAT='REVISION_FORMAT;' + notfound(b.revision_format),
327 REVISION_EXACT='REVISION_EXACT;' + str(b.revision_exact),
328 REVISIONS='REVISIONS;' + ';'.join(
329 [x.name for x in b.revisions]),
330 SOCS='SOCS;' + ';'.join([s.name for s in b.socs]),
331 IDENTIFIERS='IDENTIFIERS;' + ';'.join(identifiers)
332 )
333 print(info)
334 else:
335 print(f'{b.name}')
336
337
338def dump_boards(args):
339 arch2boards = find_arch2boards(args)
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800340 for arch, boards in arch2boards.items():
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200341 if args.cmakeformat is None:
342 print(f'{arch}:')
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800343 for board in boards:
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200344 if args.cmakeformat is not None:
345 info = args.cmakeformat.format(
346 NAME='NAME;' + board.name,
347 DIR='DIR;' + str(board.dir.as_posix()),
348 HWM='HWM;' + board.hwm,
349 VENDOR='VENDOR;NOTFOUND',
350 REVISION_DEFAULT='REVISION_DEFAULT;NOTFOUND',
351 REVISION_FORMAT='REVISION_FORMAT;NOTFOUND',
352 REVISION_EXACT='REVISION_EXACT;NOTFOUND',
353 REVISIONS='REVISIONS;NOTFOUND',
354 VARIANT_DEFAULT='VARIANT_DEFAULT;NOTFOUND',
355 SOCS='SOCS;',
356 IDENTIFIERS='IDENTIFIERS;'
357 )
358 print(info)
359 else:
360 print(f' {board.name}')
361
Martí Bolívar8bd6d082020-12-09 12:03:19 -0800362
363if __name__ == '__main__':
Torsten Rasmussen8dc3f852022-09-14 22:23:15 +0200364 args = parse_args()
365 dump_boards(args)
366 dump_v2_boards(args)