Coverage for apio/commands/apio_boards.py: 99%
107 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-07 05:08 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-07 05:08 +0000
1# This file is part of the Apio project
2# (C) 2016-present FPGAwars
3# Project authors:
4# * Jesús Arroyo (Jesus89) (2016-2019)
5# * Juan Gonzalez (obijuan) (2019-2024)
6# * The Apio Team (2024-present)
7# License: GPLv2
9"""Implementation of 'apio boards' command"""
11from pathlib import Path
12from datetime import date
13from dataclasses import dataclass
14import click
15from rich.table import Table
16from rich import box
17from apio.common.apio_console import cout, ctable, cwrite
18from apio.common.apio_styles import INFO
19from apio.common import apio_console, proto_util
20from apio.common.apio_styles import BORDER, EMPH1
21from apio.utils import util, cmd_util
22from apio.commands import options
23from apio.managers.examples import Examples
24from apio.common.proto.stubs.apio_common_pb2 import ApioArch
25from apio.apio_context import (
26 ApioContext,
27 PackagesPolicy,
28 ProjectPolicy,
29 RemoteConfigPolicy,
30)
33@dataclass(frozen=True)
34class Entry:
35 """Holds the values of a single board report line."""
37 # pylint: disable=too-many-instance-attributes
39 board_id: str
40 examples_count: str
41 board_description: str
42 fpga_arch: str
43 fpga_size: str
44 fpga_id: str
45 fpga_vendor_part: str
46 programmer: str
48 def sort_key(self):
49 """A key for sorting the fpga entries in our preferred order."""
50 return (util.fpga_arch_sort_key(self.fpga_arch), self.board_id.lower())
53def _collect_board_entries(apio_ctx: ApioContext) -> list[Entry]:
54 # -- The context is expected to have the board, fpga, and programmers
55 # -- definitions.
56 assert apio_ctx.definitions is not None
58 # -- Get examples counts by board. This is a sparse dictionary.
59 examples = Examples(apio_ctx)
60 examples_counts: dict[str, int] = examples.count_examples_by_board()
62 # -- Collect the boards info into a list of entires, one per board.
63 result: list[Entry] = []
64 for board_id, board_definition in apio_ctx.definitions.boards.items():
65 proto_util.check_is_required(
66 board_definition, "fpga_id", "description", "programmer.id"
67 )
68 fpga_id = board_definition.fpga_id
69 fpga_definition = apio_ctx.definitions.fpgas[fpga_id]
70 proto_util.check_is_required(
71 fpga_definition, "arch", "size", "vendor_part"
72 )
73 examples_count = " " + str(examples_counts.get(board_id, ""))
74 board_description = board_definition.description
75 fpga_arch = ApioArch.Name(fpga_definition.arch)
76 fpga_size = fpga_definition.size
77 fpga_vendor_part = fpga_definition.vendor_part
78 programmer_id = board_definition.programmer.id
80 result.append(
81 Entry(
82 board_id=board_id,
83 examples_count=examples_count,
84 board_description=board_description,
85 fpga_arch=fpga_arch,
86 fpga_size=fpga_size,
87 fpga_id=fpga_id,
88 fpga_vendor_part=fpga_vendor_part,
89 programmer=programmer_id,
90 )
91 )
93 # -- Sort boards by our preferred order.
94 result.sort(key=lambda x: x.sort_key())
96 # -- All done.
97 return result
100def _list_boards(apio_ctx: ApioContext, verbose: bool):
101 """Prints all the available board definitions."""
103 # -- Collect the boards info into a list of entires, one per board.
104 entries: list[Entry] = _collect_board_entries(apio_ctx)
106 # -- Define the table.
107 table = Table(
108 show_header=True,
109 show_lines=False,
110 box=box.SQUARE,
111 border_style=BORDER,
112 title_justify="left",
113 title="Apio Supported Boards",
114 )
116 # -- Add columns.
117 table.add_column("BOARD-ID", no_wrap=True, style=EMPH1)
118 table.add_column("EXMPLS", no_wrap=True)
119 if verbose:
120 table.add_column("PRODUCT", no_wrap=True, max_width=25)
121 table.add_column("ARCH", no_wrap=True)
122 table.add_column("SIZE", no_wrap=True)
123 if verbose:
124 table.add_column("FPGA-ID", no_wrap=True)
125 table.add_column("PART-NUMBER", no_wrap=True)
126 table.add_column("PROGRAMMER", no_wrap=True)
128 # -- Add rows, with separation line between architecture groups.
129 last_arch = None
130 for entry in entries:
131 # -- If switching architecture, add an horizontal separation line.
132 if last_arch != entry.fpga_arch and apio_console.is_terminal():
133 table.add_section()
134 last_arch = entry.fpga_arch
136 # -- Collect row values.
137 values = []
138 values.append(entry.board_id)
139 values.append(str(entry.examples_count))
140 if verbose:
141 values.append(entry.board_description)
142 values.append(entry.fpga_arch)
143 values.append(entry.fpga_size)
144 if verbose:
145 values.append(entry.fpga_id)
146 values.append(entry.fpga_vendor_part)
147 values.append(entry.programmer)
149 # -- Add row.
150 table.add_row(*values)
152 # -- Render the table.
153 cout()
154 ctable(table)
156 # -- Show the summary.
158 if apio_console.is_terminal(): 158 ↛ exitline 158 didn't return from function '_list_boards' because the condition on line 158 was always true
159 cout(f"Total of {util.plurality(entries, 'board')}")
160 if not verbose:
161 cout(
162 "Run 'apio boards -v' for additional columns.",
163 style=INFO,
164 )
167def _list_boards_docs_format(apio_ctx: ApioContext):
168 """Output boards information in a format for Apio Docs."""
170 # -- Get the version of the 'definitions' package use. At this point it's
171 # -- expected to be installed.
172 definitions_package_version = (
173 apio_ctx.package_manager.get_installed_package_version("definitions")
174 )
176 # -- Collect the boards info into a list of entires, one per board.
177 entries: list[Entry] = _collect_board_entries(apio_ctx)
179 # -- Determine column sizes
180 w1 = max(len("BOARD"), *(len(entry.board_id) for entry in entries))
181 w2 = max(len("SIZE"), *(len(entry.fpga_size) for entry in entries))
182 w3 = max(
183 len("DESCRIPTION"),
184 *(len(entry.board_description) for entry in entries),
185 )
186 w4 = max(len("FPGA"), *(len(entry.fpga_vendor_part) for entry in entries))
188 # -- Print page header
189 today = date.today()
190 today_str = f"{today.strftime('%B')} {today.day}, {today.year}"
191 cwrite("\n<!-- BEGIN generation by 'apio boards --docs' -->\n")
192 cwrite("\n# Supported FPGA Boards\n")
193 cwrite(
194 f"\nThis markdown page was generated automatically on {today_str} "
195 f"from version `{definitions_package_version}` of the Apio definitions package.\n"
196 )
197 cwrite(
198 "\n> Custom board definitions can be added in the project directory "
199 "can latter be contributed to Apio in the "
200 "[apio-definitions](https://github.com/FPGAwars/apio-definitions/"
201 "tree/main/definitions) repository.\n"
202 )
204 # -- Add the rows, with separation line between architecture groups.
205 last_arch = None
206 for entry in entries:
207 # -- If switching architecture, add an horizontal separation line.
208 if last_arch != entry.fpga_arch:
210 cout(f"\n## {entry.fpga_arch.upper()} boards")
212 cwrite(
213 "\n| {0} | {1} | {2} | {3} |\n".format(
214 "BOARD-ID".ljust(w1),
215 "SIZE".ljust(w2),
216 "DESCRIPTION".ljust(w3),
217 "FPGA".ljust(w4),
218 )
219 )
220 cwrite(
221 "| {0} | {1} | {2} | {3} |\n".format(
222 ":-".ljust(w1, "-"),
223 ":-".ljust(w2, "-"),
224 ":-".ljust(w3, "-"),
225 ":-".ljust(w4, "-"),
226 )
227 )
229 last_arch = entry.fpga_arch
231 cwrite(
232 "| {0} | {1} | {2} | {3} |\n".format(
233 entry.board_id.ljust(w1),
234 entry.fpga_size.ljust(w2),
235 entry.board_description.ljust(w3),
236 entry.fpga_vendor_part.ljust(w4),
237 )
238 )
240 cwrite("\n<!-- END generation by 'apio boards --docs' -->\n\n")
243# ------------- apio boards
246# -- Text in the rich-text format of the python rich library.
247APIO_BOARDS_HELP = """
248The command 'apio boards' lists the FPGA boards recognized by Apio. \
249Custom boards can be defined by placing a custom 'boards.jsonc' file in the \
250project directory, which will override Apio’s default 'boards.jsonc' file.
252Examples:[code]
253 apio boards # List all boards
254 apio boards -v # List with extra columns
255 apio boards | grep ecp5 # Filter boards results
256 apio boards --docs # Generate a report for Apio docs[/code]
258"""
261@click.command(
262 name="boards",
263 cls=cmd_util.ApioCommand,
264 short_help="List available board definitions.",
265 help=APIO_BOARDS_HELP,
266)
267@options.verbose_option
268@options.docs_format_option
269@options.project_dir_option
270def cli(
271 *,
272 # Options
273 verbose: bool,
274 docs: bool,
275 project_dir: Path | None,
276):
277 """Implements the 'boards' command which lists available board
278 definitions."""
280 # -- Determine project policy for the apio context. For docs output we
281 # -- want to ignore custom boards.
282 project_policy = (
283 ProjectPolicy.NO_PROJECT if docs else ProjectPolicy.PROJECT_OPTIONAL
284 )
286 # -- Create the apio context. If the project exists, it's custom
287 # -- boards.jsonc is also loaded. Config is required since we query
288 # -- the example package.
289 # -- We suppress the message with the env and board ids since it's
290 # -- not relevant for this command.
291 apio_ctx = ApioContext(
292 project_policy=project_policy,
293 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
294 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
295 project_dir_arg=project_dir,
296 report_env=False,
297 )
299 if docs:
300 _list_boards_docs_format(apio_ctx)
301 else:
302 _list_boards(apio_ctx, verbose)