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