Coverage for apio / commands / apio_api.py: 88%
257 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-24 01:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-24 01: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 api' command"""
10import sys
11import os
12from typing import Dict, List, Self, Optional
13from dataclasses import dataclass
14import json
15from pathlib import Path
16import click
17from apio.commands import options
19# from apio.managers import packages
20from apio.managers.examples import Examples, ExampleInfo
21from apio.common.apio_console import cout, cerror
22from apio.common.apio_styles import INFO
23from apio.common.common_util import get_project_source_files
24from apio.utils import cmd_util, usb_util, serial_util, util
25from apio.utils.usb_util import UsbDevice
26from apio.utils.serial_util import SerialDevice
27from apio.apio_context import (
28 ApioContext,
29 PackagesPolicy,
30 ProjectPolicy,
31 RemoteConfigPolicy,
32)
33from apio.utils.cmd_util import (
34 ApioGroup,
35 ApioSubgroup,
36 ApioCommand,
37 ApioCmdContext,
38)
41timestamp_option = click.option(
42 "timestamp", # Var name.
43 "-t",
44 "--timestamp",
45 type=str,
46 metavar="text",
47 help="Set a user provided timestamp.",
48 cls=cmd_util.ApioOption,
49)
51output_option = click.option(
52 "output", # Var name.
53 "-o",
54 "--output",
55 type=str,
56 metavar="file-name",
57 help="Set output file.",
58 cls=cmd_util.ApioOption,
59)
62def write_as_json_doc(top_dict: Dict, output_flag: str, force_flag: bool):
63 """A common function to write a dict as a JSON doc."""
64 # -- Format the top dict as json text.
65 text = json.dumps(top_dict, indent=2)
67 if output_flag:
68 # -- Output the json text to a user specified file.
69 output_path = Path(output_flag)
71 if output_path.is_dir(): 71 ↛ 72line 71 didn't jump to line 72 because the condition on line 71 was never true
72 cerror(f"The output path {output_path} is a directory.")
73 sys.exit(1)
75 if output_path.exists() and not force_flag: 75 ↛ 76line 75 didn't jump to line 76 because the condition on line 75 was never true
76 cerror(f"The file already exists {output_path}.")
77 cout("Use the --force option to allow overwriting.", style=INFO)
78 sys.exit(1)
80 # -- if there file path contains a parent dir, make
81 # -- sure it exists. If output_flag is just a file name such
82 # -- as 'foo.json', we don nothing.
83 dirname = os.path.dirname(output_flag)
84 if dirname: 84 ↛ 88line 84 didn't jump to line 88 because the condition on line 84 was always true
85 os.makedirs(dirname, exist_ok=True)
87 # -- Write to file.
88 with open(output_flag, "w", encoding="utf-8") as f:
89 f.write(text)
90 else:
91 # -- Output the json text to stdout.
92 print(text, file=sys.stdout)
95# ------ apio api get-system
98# -- Text in the rich-text format of the python rich library.
99APIO_API_GET_SYSTEM_HELP = """
100The command 'apio api get-system' exports information about apio and \
101the underlying system as a JSON foc. It is similar to the command \
102'apio info system' which is intended for human consumption.
104The optional flag '--timestamp' allows the caller to embed in the JSON \
105document a known timestamp that allows to verify that the JSON document \
106was indeed was generated by the same invocation.
108Examples:[code]
109 apio api get-system # Write to stdout
110 apio api get-system -o apio.json # Write to a file[/code]
111"""
114@click.command(
115 name="get-system",
116 cls=ApioCommand,
117 short_help="Retrieve apio and system information.",
118 help=APIO_API_GET_SYSTEM_HELP,
119)
120# @click.pass_context
121@timestamp_option
122@output_option
123@options.force_option_gen(short_help="Overwrite output file.")
124def _get_system_cli(
125 # Options
126 timestamp: str,
127 output: str,
128 force: bool,
129):
130 """Implements the 'apio apio get-system' command."""
132 apio_ctx = ApioContext(
133 project_policy=ProjectPolicy.NO_PROJECT,
134 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
135 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
136 )
138 # -- The top dict that we will emit as json.
139 top_dict = {}
141 # -- Append user timestamp if specified.
142 if timestamp: 142 ↛ 145line 142 didn't jump to line 145 because the condition on line 142 was always true
143 top_dict["timestamp"] = timestamp
145 section_dict = {}
147 # -- Add fields.
148 section_dict["apio-version"] = util.get_apio_version_str()
149 section_dict["python-version"] = util.get_python_version()
150 section_dict["python-executable"] = sys.executable
151 section_dict["platform-id"] = apio_ctx.platform_id
152 section_dict["scons-shell-id"] = apio_ctx.scons_shell_id
153 section_dict["vscode-debugger"] = str(
154 util.is_under_vscode_debugger()
155 ).lower()
156 section_dict["pyinstaller"] = str(util.is_pyinstaller_app()).lower()
157 section_dict["apio-python_package"] = str(
158 util.get_path_in_apio_package("")
159 )
160 section_dict["apio-home-dir"] = str(apio_ctx.apio_home_dir)
161 section_dict["apio-packages-dir"] = str(apio_ctx.apio_packages_dir)
162 section_dict["remote-config-url"] = apio_ctx.profile.remote_config_url
163 section_dict["verible-formatter"] = str(
164 apio_ctx.apio_packages_dir / "verible/bin/verible-verilog-format"
165 )
166 section_dict["verible-language-server"] = str(
167 apio_ctx.apio_packages_dir / "verible/bin/verible-verilog-ls"
168 )
170 # -- Add section
171 top_dict["system"] = section_dict
173 # -- Write out
174 write_as_json_doc(top_dict, output, force)
177# ------ apio api get-project
180# -- Text in the rich-text format of the python rich library.
181APIO_API_GET_PROJECT_HELP = """
182The command 'apio api get-project' exports information about an Apio
183project as a JSON foc.
185The optional flag '--timestamp' allows the caller to embed in the JSON \
186document a known timestamp that allows to verify that the JSON document \
187was indeed was generated by the same invocation.
189Examples:[code]
190 apio api get-project # Report default env
191 apio api get-project -e env1 # Report specified env
192 apio api get-project -p foo/bar # Project in another dir
193 apio api get-project -o apio.json # Write to a file[/code]
194"""
197@click.command(
198 name="get-project",
199 cls=ApioCommand,
200 short_help="Get project information.",
201 help=APIO_API_GET_PROJECT_HELP,
202)
203# @click.pass_context
204@options.env_option_gen()
205@options.project_dir_option
206@timestamp_option
207@output_option
208@options.force_option_gen(short_help="Overwrite output file.")
209def _get_project_cli(
210 # Options
211 env: str,
212 project_dir: Optional[Path],
213 timestamp: str,
214 output: str,
215 force: bool,
216):
217 """Implements the 'apio apio get-project' command."""
219 apio_ctx = ApioContext(
220 project_policy=ProjectPolicy.PROJECT_REQUIRED,
221 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
222 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
223 project_dir_arg=project_dir,
224 env_arg=env,
225 )
227 # -- Change to the project's folder.
228 os.chdir(apio_ctx.project_dir)
230 # -- The top dict that we will emit as json.
231 top_dict = {}
233 # -- Append user timestamp if specified.
234 if timestamp: 234 ↛ 237line 234 didn't jump to line 237 because the condition on line 234 was always true
235 top_dict["timestamp"] = timestamp
237 section_dict = {}
239 active_env_dict = {}
240 active_env_dict["name"] = apio_ctx.project.env_name
241 active_env_dict["options"] = apio_ctx.project.env_options
242 section_dict["active-env"] = active_env_dict
244 section_dict["envs"] = apio_ctx.project.env_names
246 synth_srcs, test_srcs = get_project_source_files()
247 section_dict["synth-files"] = synth_srcs
248 section_dict["test-benches"] = test_srcs
250 # -- Add section
251 top_dict["project"] = section_dict
253 # -- Write out
254 write_as_json_doc(top_dict, output, force)
257# ------ apio api get-boards
260# -- Text in the rich-text format of the python rich library.
261APIO_API_GET_BOARDS_HELP = """
262The command 'apio api get-boards' exports apio boards information as a \
263JSON document.
265The optional flag '--timestamp' allows the caller to embed in the JSON \
266document a known timestamp that allows to verify that the JSON document \
267was indeed was generated by the same invocation.
269Examples:[code]
270 apio api get-boards # Write to stdout
271 apio api get-boards -o apio.json # Write to a file[/code]
272"""
275@click.command(
276 name="get-boards",
277 cls=ApioCommand,
278 short_help="Retrieve boards information.",
279 help=APIO_API_GET_BOARDS_HELP,
280)
281@timestamp_option
282@output_option
283@options.force_option_gen(short_help="Overwrite output file.")
284def _get_boards_cli(
285 # Options
286 timestamp: str,
287 output: str,
288 force: bool,
289):
290 """Implements the 'apio apio get-boards' command."""
292 # -- For now, the information is not in a project context. That may
293 # -- change in the future.
294 apio_ctx = ApioContext(
295 project_policy=ProjectPolicy.NO_PROJECT,
296 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
297 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
298 )
300 # -- The top dict that we will emit as json.
301 top_dict = {}
303 # -- Append user timestamp if specified.
304 if timestamp: 304 ↛ 308line 304 didn't jump to line 308 because the condition on line 304 was always true
305 top_dict["timestamp"] = timestamp
307 # -- Generate the boards section.
308 section = {}
309 for board_id, board_info in apio_ctx.boards.items():
310 # -- The board output dict.
311 board_dict = {}
313 # -- Add board description
314 board_dict["description"] = board_info.get("description", None)
316 # -- Add board's fpga information.
317 fpga_dict = {}
318 fpga_id = board_info.get("fpga-id", None)
319 fpga_info = apio_ctx.fpgas.get(fpga_id, {})
320 fpga_dict["id"] = fpga_id
321 fpga_dict["part-num"] = fpga_info.get("part-num", None)
322 fpga_dict["arch"] = fpga_info.get("arch", None)
323 fpga_dict["size"] = fpga_info.get("size", None)
324 board_dict["fpga"] = fpga_dict
326 # -- Add board's programmer information.
327 programmer_dict = {}
328 programmer_id = board_info.get("programmer", {}).get("id", None)
329 programmer_dict["id"] = programmer_id
330 board_dict["programmer"] = programmer_dict
332 # -- Add the board to the boards dict.
333 section[board_id] = board_dict
335 top_dict["boards"] = section
337 # -- Write out
338 write_as_json_doc(top_dict, output, force)
341# ------ apio api get-fpgas
344# -- Text in the rich-text format of the python rich library.
345APIO_API_GET_FPGAS_HELP = """
346The command 'apio api get-fpgas' exports apio FPGAss information as a \
347JSON document.
349The optional flag '--timestamp' allows the caller to embed in the JSON \
350document a known timestamp that allows to verify that the JSON document \
351was indeed was generated by the same invocation.
353Examples:[code]
354 apio api get-fpgas # Write to stdout
355 apio api get-fpgas -o apio.json # Write to a file[/code]
356"""
359@click.command(
360 name="get-fpgas",
361 cls=ApioCommand,
362 short_help="Retrieve FPGAs information.",
363 help=APIO_API_GET_FPGAS_HELP,
364)
365@timestamp_option
366@output_option
367@options.force_option_gen(short_help="Overwrite output file.")
368def _get_fpgas_cli(
369 # Options
370 timestamp: str,
371 output: str,
372 force: bool,
373):
374 """Implements the 'apio apio get-fpgas' command."""
376 # -- For now, the information is not in a project context. That may
377 # -- change in the future.
378 apio_ctx = ApioContext(
379 project_policy=ProjectPolicy.NO_PROJECT,
380 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
381 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
382 )
384 # -- The top dict that we will emit as json.
385 top_dict = {}
387 # -- Append user timestamp if specified.
388 if timestamp: 388 ↛ 392line 388 didn't jump to line 392 because the condition on line 388 was always true
389 top_dict["timestamp"] = timestamp
391 # -- Generate the fpgas section
392 section = {}
393 for fpga_id, fpga_info in apio_ctx.fpgas.items():
394 # -- The fpga output dict.
395 fpga_dict = {}
397 fpga_dict["part-num"] = fpga_info.get("part-num", None)
398 fpga_dict["arch"] = fpga_info.get("arch", None)
399 fpga_dict["size"] = fpga_info.get("size", None)
401 # -- Add the fpga to the fpgas dict.
402 section[fpga_id] = fpga_dict
404 top_dict["fpgas"] = section
406 # -- Write out
407 write_as_json_doc(top_dict, output, force)
410# ------ apio api get-programmers
413# -- Text in the rich-text format of the python rich library.
414APIO_API_GET_PROGRAMMERS_HELP = """
415The command 'apio api get-programmers' exports apio programmers information \
416as a JSON document.
418The optional flag '--timestamp' allows the caller to embed in the JSON \
419document a known timestamp that allows to verify that the JSON document \
420was indeed was generated by the same invocation.
422Examples:[code]
423 apio api get-programmers # Write to stdout
424 apio api get-programmers -o apio.json # Write to a file[/code]
425"""
428@click.command(
429 name="get-programmers",
430 cls=ApioCommand,
431 short_help="Retrieve programmers information.",
432 help=APIO_API_GET_PROGRAMMERS_HELP,
433)
434@timestamp_option
435@output_option
436@options.force_option_gen(short_help="Overwrite output file.")
437def _get_programmers_cli(
438 # Options
439 timestamp: str,
440 output: str,
441 force: bool,
442):
443 """Implements the 'apio apio get-programmers' command."""
445 # -- For now, the information is not in a project context. That may
446 # -- change in the future.
447 apio_ctx = ApioContext(
448 project_policy=ProjectPolicy.NO_PROJECT,
449 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
450 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
451 )
453 # -- The top dict that we will emit as json.
454 top_dict = {}
456 # -- Append user timestamp if specified.
457 if timestamp: 457 ↛ 461line 457 didn't jump to line 461 because the condition on line 457 was always true
458 top_dict["timestamp"] = timestamp
460 # -- Generate the 'programmers' section.
461 section = {}
462 for programmer_id, programmer_info in apio_ctx.programmers.items():
463 section[programmer_id] = programmer_info
465 top_dict["programmers"] = section
467 # -- Write out
468 write_as_json_doc(top_dict, output, force)
471# ------ apio api get-examples
474# -- Text in the rich-text format of the python rich library.
475APIO_API_GET_EXAMPLES_HELP = """
476The command 'apio api get-examples' exports apio examples information as a \
477JSON document.
479The optional flag '--timestamp' allows the caller to embed in the JSON \
480document a known timestamp that allows to verify that the JSON document \
481was indeed was generated by the same invocation.
483Examples:[code]
484 apio api get-examples # Write to stdout
485 apio api get-examples -o apio.json # Write to a file[/code]
486"""
489@click.command(
490 name="get-examples",
491 cls=ApioCommand,
492 short_help="Retrieve examples information.",
493 help=APIO_API_GET_EXAMPLES_HELP,
494)
495@timestamp_option
496@output_option
497@options.force_option_gen(short_help="Overwrite output file.")
498def _get_examples_cli(
499 # Options
500 timestamp: str,
501 output: str,
502 force: bool,
503):
504 """Implements the 'apio apio get-examples' command."""
506 # -- For now, the information is not in a project context. That may
507 # -- change in the future.
508 apio_ctx = ApioContext(
509 project_policy=ProjectPolicy.NO_PROJECT,
510 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
511 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
512 )
514 # -- Get examples infos.
515 examples: List[ExampleInfo] = Examples(apio_ctx).get_examples_infos()
517 # -- Group examples by boards
518 boards_examples: Dict[str, List[ExampleInfo]] = {}
519 for example in examples:
520 board_examples = boards_examples.get(example.board_id, [])
521 board_examples.append(example)
522 boards_examples[example.board_id] = board_examples
524 # -- The top dict that we will emit as json.
525 top_dict = {}
527 # -- Append user timestamp if specified.
528 if timestamp: 528 ↛ 532line 528 didn't jump to line 532 because the condition on line 528 was always true
529 top_dict["timestamp"] = timestamp
531 # -- Generate the 'examples' section.
532 section = {}
533 for board, board_examples in boards_examples.items():
534 board_dict = {}
535 # -- Generate board examples
536 for example_info in board_examples:
537 example_dict = {}
538 example_dict["description"] = example_info.description
539 board_dict[example_info.example_name] = example_dict
541 section[board] = board_dict
543 top_dict["examples"] = section
545 # -- Write out
546 write_as_json_doc(top_dict, output, force)
549# ------ apio api get-commands
552@dataclass(frozen=True)
553class CmdInfo:
554 """Represents the information of a single apio command."""
556 name: str
557 path: List[str]
558 cli: click.Command
559 children: List[Self]
562def scan_children(cmd_cli) -> Dict:
563 """Return a dict describing this command subtree."""
564 result = {}
566 # -- Sanity check
567 assert isinstance(result, dict), type(result)
569 # -- If this is a simple command, it has no sub commands.
570 if isinstance(cmd_cli, ApioCommand):
571 return result
573 # -- Here we have a group and it should have at least one sub command.
574 assert isinstance(cmd_cli, ApioGroup), type(cmd_cli)
575 subgroups: List[ApioSubgroup] = cmd_cli.subgroups
577 # -- Create the dict for the command subgroups.
578 subcommands_dict = {}
579 result["commands"] = subcommands_dict
581 # -- Iterate the subgroups and populate them. We flaten the subcommands
582 # -- group into a single list of commands.
583 for subgroup in subgroups:
584 assert isinstance(subgroup, ApioSubgroup), type(subgroup)
585 assert isinstance(subgroup.title, str), type(subgroup.title)
586 for subcommand in subgroup.commands:
587 subcommand_dict = scan_children(subcommand)
588 subcommands_dict[subcommand.name] = subcommand_dict
590 # -- All done ok.
591 return result
594# -- Text in the rich-text format of the python rich library.
595APIO_API_GET_COMMANDS_HELP = """
596The command 'apio api get-commands' exports apio command structure \
597of Apio as a JSON doc. This is used by various tools such as
598documentation generators and tests.
600The optional flag '--timestamp' allows the caller to embed in the JSON \
601document a known timestamp that allows to verify that the JSON document \
602was indeed was generated by the same invocation.
604Examples:[code]
605 apio api get-commands # Write to stdout
606 apio api get-commands -o apio.json # Write to a file[/code]
607"""
610@click.command(
611 name="get-commands",
612 cls=ApioCommand,
613 short_help="Retrieve apio commands information.",
614 help=APIO_API_GET_COMMANDS_HELP,
615)
616@click.pass_context
617@timestamp_option
618@output_option
619@options.force_option_gen(short_help="Overwrite output file.")
620def _get_commands_cli(
621 # Click context
622 cmd_ctx: ApioCmdContext,
623 # Options
624 timestamp: str,
625 output: str,
626 force: bool,
627):
628 """Implements the 'apio apio get-commands' command."""
630 # -- Find the top cli which is the "apio" command. Would access it
631 # -- directly but it would create a circular python import.
632 ctx = cmd_ctx
633 while ctx.parent:
634 ctx = ctx.parent
635 assert isinstance(ctx, ApioCmdContext), type(ctx)
636 top_cli = ctx.command
637 assert top_cli.name == "apio", top_cli
639 # -- This initializes the console, print active env vars, etc.
640 ApioContext(
641 project_policy=ProjectPolicy.NO_PROJECT,
642 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
643 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
644 )
646 # -- The top dict that we will emit as json.
647 top_dict = {}
649 # -- Append user timestamp if specified.
650 if timestamp: 650 ↛ 653line 650 didn't jump to line 653 because the condition on line 650 was always true
651 top_dict["timestamp"] = timestamp
653 section_dict = {}
654 section_dict["apio"] = scan_children(top_cli)
655 top_dict["commands"] = section_dict
657 # -- Write out
658 write_as_json_doc(top_dict, output, force)
661# ------ apio api scan-devices
664# -- Text in the rich-text format of the python rich library.
665APIO_API_SCAN_DEVICES_HELP = """
666The command 'apio api scan-devices' scans and report the available usb and \
667serial devices.
669The optional flag '--timestamp' allows the caller to embed in the JSON \
670document a known timestamp that allows to verify that the JSON document \
671was indeed was generated by the same invocation.
673Examples:[code]
674 apio api scan-devices # Write to stdout
675 apio api scan-devices -o apio.json # Write to a file[/code]
676"""
679@click.command(
680 name="scan-devices",
681 cls=ApioCommand,
682 short_help="Scan and report available devices.",
683 help=APIO_API_SCAN_DEVICES_HELP,
684)
685@timestamp_option
686@output_option
687@options.force_option_gen(short_help="Overwrite output file.")
688def _scan_devices_cli(
689 # Options
690 timestamp: str,
691 output: str,
692 force: bool,
693):
694 """Implements the 'apio apio scan-devices' command."""
696 # -- For now, the information is not in a project context. That may
697 # -- change in the future. We need the config since we use libusb from
698 # -- the packages.
699 apio_ctx = ApioContext(
700 project_policy=ProjectPolicy.NO_PROJECT,
701 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
702 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
703 )
705 # -- The top dict that we will emit as json.
706 top_dict = {}
708 # -- Append user timestamp if specified.
709 if timestamp: 709 ↛ 715line 709 didn't jump to line 715 because the condition on line 709 was always true
710 top_dict["timestamp"] = timestamp
712 # -- We need the packages for the 'libusb' backend.
713 # packages.install_missing_packages_on_the_fly(apio_ctx.packages_context)
715 usb_devices: List[UsbDevice] = usb_util.scan_usb_devices(apio_ctx)
717 # -- Scan and report usb devices.
718 section = []
719 for device in usb_devices: 719 ↛ 720line 719 didn't jump to line 720 because the loop on line 719 never started
720 dev = {}
721 dev["vid"] = device.vendor_id
722 dev["pid"] = device.product_id
723 dev["bus"] = device.bus
724 dev["device"] = device.device
725 dev["manufacturer"] = device.manufacturer
726 dev["product"] = device.product
727 dev["serial-number"] = device.serial_number
728 dev["device_type"] = device.device_type
730 section.append(dev)
732 top_dict["usb-devices"] = section
734 # -- Scan and report serial devices.
735 serial_devices: List[SerialDevice] = serial_util.scan_serial_devices()
737 section = []
738 for device in serial_devices: 738 ↛ 739line 738 didn't jump to line 739 because the loop on line 738 never started
739 dev = {}
740 dev["port"] = device.port
741 dev["port-name"] = device.port_name
742 dev["vendor-id"] = device.vendor_id
743 dev["product-id"] = device.product_id
744 dev["manufacturer"] = device.manufacturer
745 dev["product"] = device.product
746 dev["serial-number"] = device.serial_number
747 dev["device-type"] = device.device_type
749 section.append(dev)
751 top_dict["serial-devices"] = section
753 # -- Write out
754 write_as_json_doc(top_dict, output, force)
757# ------ apio apio
759# -- Text in the rich-text format of the python rich library.
760APIO_API_HELP = """
761The command group 'apio api' contains subcommands that that are intended \
762to be used by tools and programs such as icestudio, rather than being used \
763directly by users.
764"""
766# -- We have only a single group with the title 'Subcommands'.
767SUBGROUPS = [
768 ApioSubgroup(
769 "Subcommands",
770 [
771 _get_system_cli,
772 _get_project_cli,
773 _get_boards_cli,
774 _get_fpgas_cli,
775 _get_programmers_cli,
776 _get_examples_cli,
777 _get_commands_cli,
778 _scan_devices_cli,
779 ],
780 )
781]
784@click.command(
785 name="api",
786 cls=ApioGroup,
787 subgroups=SUBGROUPS,
788 short_help="Apio programmatic interface.",
789 help=APIO_API_HELP,
790)
791def cli():
792 """Implements the 'apio apio' command group."""
794 # pass