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