1# Copyright (c) 2024 The Linux Foundation
2# SPDX-License-Identifier: Apache-2.0
3
4import logging
5from collections import namedtuple
6from pathlib import Path
7
8import list_boards
9import list_hardware
10import yaml
11import zephyr_module
12from gen_devicetree_rest import VndLookup
13
14ZEPHYR_BASE = Path(__file__).parents[2]
15
16logger = logging.getLogger(__name__)
17
18
19def guess_file_from_patterns(directory, patterns, name, extensions):
20    for pattern in patterns:
21        for ext in extensions:
22            matching_file = next(directory.glob(pattern.format(name=name, ext=ext)), None)
23            if matching_file:
24                return matching_file
25    return None
26
27
28def guess_image(board_or_shield):
29    img_exts = ["jpg", "jpeg", "webp", "png"]
30    patterns = [
31        "**/{name}.{ext}",
32        "**/*{name}*.{ext}",
33        "**/*.{ext}",
34    ]
35    img_file = guess_file_from_patterns(
36        board_or_shield.dir, patterns, board_or_shield.name, img_exts
37    )
38
39    return (img_file.relative_to(ZEPHYR_BASE)).as_posix() if img_file else None
40
41def guess_doc_page(board_or_shield):
42    patterns = [
43        "doc/index.{ext}",
44        "**/{name}.{ext}",
45        "**/*{name}*.{ext}",
46        "**/*.{ext}",
47    ]
48    doc_file = guess_file_from_patterns(
49        board_or_shield.dir, patterns, board_or_shield.name, ["rst"]
50    )
51    return doc_file
52
53
54def get_catalog():
55    vnd_lookup = VndLookup(ZEPHYR_BASE / "dts/bindings/vendor-prefixes.txt", [])
56
57    module_settings = {
58        "arch_root": [ZEPHYR_BASE],
59        "board_root": [ZEPHYR_BASE],
60        "soc_root": [ZEPHYR_BASE],
61    }
62
63    for module in zephyr_module.parse_modules(ZEPHYR_BASE):
64        for key in module_settings:
65            root = module.meta.get("build", {}).get("settings", {}).get(key)
66            if root is not None:
67                module_settings[key].append(Path(module.project) / root)
68
69    Args = namedtuple("args", ["arch_roots", "board_roots", "soc_roots", "board_dir", "board"])
70    args_find_boards = Args(
71        arch_roots=module_settings["arch_root"],
72        board_roots=module_settings["board_root"],
73        soc_roots=module_settings["soc_root"],
74        board_dir=[],
75        board=None,
76    )
77
78    boards = list_boards.find_v2_boards(args_find_boards)
79    systems = list_hardware.find_v2_systems(args_find_boards)
80    board_catalog = {}
81
82    for board in boards.values():
83        # We could use board.vendor but it is often incorrect. Instead, deduce vendor from
84        # containing folder. There are a few exceptions, like the "native" and "others" folders
85        # which we know are not actual vendors so treat them as such.
86        for folder in board.dir.parents:
87            if folder.name in ["native", "others"]:
88                vendor = "others"
89                break
90            elif vnd_lookup.vnd2vendor.get(folder.name):
91                vendor = folder.name
92                break
93
94        # Grab all the twister files for this board and use them to figure out all the archs it
95        # supports.
96        archs = set()
97        pattern = f"{board.name}*.yaml"
98        for twister_file in board.dir.glob(pattern):
99            try:
100                with open(twister_file) as f:
101                    board_data = yaml.safe_load(f)
102                    archs.add(board_data.get("arch"))
103            except Exception as e:
104                logger.error(f"Error parsing twister file {twister_file}: {e}")
105
106        socs = {soc.name for soc in board.socs}
107        full_name = board.full_name or board.name
108        doc_page = guess_doc_page(board)
109
110        board_catalog[board.name] = {
111            "name": board.name,
112            "full_name": full_name,
113            "doc_page": doc_page.relative_to(ZEPHYR_BASE).as_posix() if doc_page else None,
114            "vendor": vendor,
115            "archs": list(archs),
116            "socs": list(socs),
117            "image": guess_image(board),
118        }
119
120    socs_hierarchy = {}
121    for soc in systems.get_socs():
122        family = soc.family or "<no family>"
123        series = soc.series or "<no series>"
124        socs_hierarchy.setdefault(family, {}).setdefault(series, []).append(soc.name)
125
126    return {
127        "boards": board_catalog,
128        "vendors": {**vnd_lookup.vnd2vendor, "others": "Other/Unknown"},
129        "socs": socs_hierarchy,
130    }
131