1#!/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
8import itertools
9from pathlib import Path
10from typing import NamedTuple
11
12#
13# This is shared code between the build system's 'boards' target
14# and the 'west boards' extension command. If you change it, make
15# sure to test both ways it can be used.
16#
17# (It's done this way to keep west optional, making it possible to run
18# 'ninja boards' in a build directory without west installed.)
19#
20
21class Board(NamedTuple):
22    name: str
23    arch: str
24    dir: Path
25
26def board_key(board):
27    return board.name
28
29def find_arch2boards(args):
30    arch2board_set = find_arch2board_set(args)
31    return {arch: sorted(arch2board_set[arch], key=board_key)
32            for arch in arch2board_set}
33
34def find_boards(args):
35    return sorted(itertools.chain(*find_arch2board_set(args).values()),
36                  key=board_key)
37
38def find_arch2board_set(args):
39    arches = sorted(find_arches(args))
40    ret = defaultdict(set)
41
42    for root in args.board_roots:
43        for arch, boards in find_arch2board_set_in(root, arches).items():
44            ret[arch] |= boards
45
46    return ret
47
48def find_arches(args):
49    arch_set = set()
50
51    for root in args.arch_roots:
52        arch_set |= find_arches_in(root)
53
54    return arch_set
55
56def find_arches_in(root):
57    ret = set()
58    arch = root / 'arch'
59    common = arch / 'common'
60
61    if not arch.is_dir():
62        return ret
63
64    for maybe_arch in arch.iterdir():
65        if not maybe_arch.is_dir() or maybe_arch == common:
66            continue
67        ret.add(maybe_arch.name)
68
69    return ret
70
71def find_arch2board_set_in(root, arches):
72    ret = defaultdict(set)
73    boards = root / 'boards'
74
75    for arch in arches:
76        if not (boards / arch).is_dir():
77            continue
78
79        for maybe_board in (boards / arch).iterdir():
80            if not maybe_board.is_dir():
81                continue
82            for maybe_defconfig in maybe_board.iterdir():
83                file_name = maybe_defconfig.name
84                if file_name.endswith('_defconfig'):
85                    board_name = file_name[:-len('_defconfig')]
86                    ret[arch].add(Board(board_name, arch, maybe_board))
87
88    return ret
89
90def parse_args():
91    parser = argparse.ArgumentParser(allow_abbrev=False)
92    add_args(parser)
93    return parser.parse_args()
94
95def add_args(parser):
96    # Remember to update west-completion.bash if you add or remove
97    # flags
98    parser.add_argument("--arch-root", dest='arch_roots', default=[],
99                        type=Path, action='append',
100                        help='add an architecture root, may be given more than once')
101    parser.add_argument("--board-root", dest='board_roots', default=[],
102                        type=Path, action='append',
103                        help='add a board root, may be given more than once')
104
105def dump_boards(arch2boards):
106    for arch, boards in arch2boards.items():
107        print(f'{arch}:')
108        for board in boards:
109            print(f'  {board.name}')
110
111if __name__ == '__main__':
112    dump_boards(find_arch2boards(parse_args()))
113