Coverage for apio/commands/apio_packages.py: 81%
70 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 packages' command"""
11import sys
12import click
13from rich.table import Table
14from rich import box
15from apio.common.apio_console import cout, ctable, fatal_error
16from apio.common.apio_styles import INFO, BORDER, ERROR, SUCCESS
17from apio.commands import options
18from apio.managers.package_manager import (
19 PackagesScanResults,
20)
21from apio.utils.cmd_util import (
22 ApioGroup,
23 ApioSubgroup,
24 ApioCommand,
25 ApioOption,
26)
27from apio.apio_context import (
28 ApioContext,
29 ProjectPolicy,
30 RemoteConfigPolicy,
31 PackagesPolicy,
32)
35def print_packages_report2(apio_ctx: ApioContext) -> bool:
36 """A common function to print the state of the packages.
37 Returns True if the packages are OK.
38 """
40 # -- Scan the packages
41 # scan = packages.scan_packages(apio_ctx.package_manager)
42 scan: PackagesScanResults = apio_ctx.package_manager.scan_packages()
44 # ===== Required Packages Table =====
46 table = Table(
47 show_header=True,
48 show_lines=True,
49 box=box.SQUARE,
50 border_style=BORDER,
51 title="Apio Packages Status",
52 title_justify="left",
53 padding=(0, 2),
54 )
56 table.add_column("PACKAGE NAME", no_wrap=True)
57 table.add_column("VERSION", no_wrap=True)
58 # table.add_column("PLATFORM", no_wrap=True)
59 table.add_column("DESCRIPTION", no_wrap=True)
60 table.add_column("STATUS", no_wrap=True)
62 for package_name, package_status in scan.platform_packages.items():
64 # -- Collect additional info about the package.
65 package_manager = apio_ctx.package_manager
66 installed_version, *_ = package_manager.get_installed_package_info(
67 package_name
68 )
69 package_spec = package_manager.get_required_package_spec(package_name)
71 # -- Determine row color
72 row_style = (
73 ERROR
74 if package_status.is_inconsistency
75 else INFO if not package_status.is_ok else None
76 )
78 # -- Add a table row for the package.
79 table.add_row(
80 package_name,
81 installed_version,
82 # package_info["description"],
83 package_spec.description,
84 package_status.value,
85 style=row_style,
86 )
88 # -- Render table.
89 cout()
90 ctable(table)
92 # ===== Orphans Table =====
94 # -- Define errors table.
95 table = Table(
96 show_header=True,
97 show_lines=True,
98 box=box.SQUARE,
99 border_style=BORDER,
100 title="Apio Packages Errors",
101 title_justify="left",
102 padding=(0, 2),
103 )
105 # -- Add columns.
106 table.add_column("ERROR TYPE", no_wrap=True, min_width=15, style=ERROR)
107 table.add_column("NAME", no_wrap=True, min_width=15)
109 # -- Add rows.
110 for orphan_name, orphan_type in scan.orphans.items(): 110 ↛ 111line 110 didn't jump to line 111 because the loop on line 110 never started
111 table.add_row(orphan_name, orphan_type.value)
113 # -- Render the table, unless empty.
114 if table.row_count: 114 ↛ 115line 114 didn't jump to line 115 because the condition on line 114 was never true
115 cout()
116 ctable(table)
118 # -- Scan packages again and print a summary.
119 packages_ok = scan.is_all_ok()
121 cout()
122 if packages_ok: 122 ↛ 125line 122 didn't jump to line 125 because the condition on line 122 was always true
123 cout("All Apio packages are installed OK.", style=SUCCESS)
124 else:
125 cout(
126 "Run 'apio packages install' to install the packages.",
127 style=INFO,
128 )
130 # -- Return with the current packages status. Normally it should be
131 # -- True for OK since we fixed and installed the packages.
132 return packages_ok
135# ------ apio packages install
137# -- Text in the rich-text format of the python rich library.
138APIO_PACKAGES_INSTALL_HELP = """
139The command 'apio packages install' installs the installed Apio packages \
140to their latest requirements.
142Examples:[code]
143 apio packages install # Install packages
144 apio pack upd # Same, with shortcuts
145 apio packages install --force # Force reinstallation from scratch
146 apio packages install --verbose # Provide additional info[/code]
148Adding the '--force' option forces the reinstallation of existing packages; \
149otherwise, packages that are already installed correctly remain unchanged.
151It is highly recommended to run the 'apio packages install' once in a while \
152because it check the Apio remote server for the latest packages versions \
153which may included fixes and enhancements such as new examples that were \
154added to the examples package.
155"""
158@click.command(
159 name="install",
160 cls=ApioCommand,
161 short_help="Install apio packages.",
162 help=APIO_PACKAGES_INSTALL_HELP,
163)
164@options.force_option_gen(short_help="Force reinstallation.")
165@options.verbose_option
166def _install_cli(
167 *,
168 # Options
169 force: bool,
170 verbose: bool,
171):
172 """Implements the 'apio packages install' command."""
174 apio_ctx = ApioContext(
175 project_policy=ProjectPolicy.NO_PROJECT,
176 remote_config_policy=RemoteConfigPolicy.GET_FRESH,
177 packages_policy=PackagesPolicy.IGNORE_PACKAGES,
178 )
180 # -- First thing, fix broken packages, if any. This forces fetching
181 # -- of the latest remote config file.
182 apio_ctx.package_manager.scan_and_fix_inconsistencies()
184 # -- Install the packages, one by one.
185 for package in apio_ctx.platform_packages:
186 apio_ctx.package_manager.install_package(
187 package_name=package,
188 force_reinstall=force,
189 verbose=verbose,
190 )
192 # -- If verbose, print a full report.
193 if verbose: 193 ↛ 194line 193 didn't jump to line 194 because the condition on line 193 was never true
194 package_ok = print_packages_report2(apio_ctx)
195 if not package_ok:
196 sys.exit(1)
198 # -- When not in verbose mode, we run a scan and print a short status.
199 else:
200 # scan = packages.scan_packages(apio_ctx.package_manager)
201 scan = apio_ctx.package_manager.scan_packages()
202 if not scan.is_all_ok(): 202 ↛ 203line 202 didn't jump to line 203 because the condition on line 202 was never true
203 fatal_error(
204 "Failed to install some packages.",
205 info="Run 'apio packages list' to view the packages.",
206 )
208 # -- In the verbose case above, this is already printed by
209 # -- the print_packages_report() method.
210 cout("All Apio packages are installed OK.", style=SUCCESS)
212 # -- We believe that we have the exactly the correct packages
213 # -- installed. Perform a few final checks.
214 apio_ctx.package_manager.check_packages_post_install()
217# ------ apio packages list
219# -- Text in the rich-text format of the python rich library.
220APIO_PACKAGES_LIST_HELP = """
221The command 'apio packages list' lists the available and installed Apio \
222packages. The list of available packages depends on the operating system \
223you are using and may vary between operating systems.
225The option '--check' causes the command to fail and exit with an error \
226status code if the packages are not installed properly.
228Examples:[code]
229 apio packages list # Just report
230 apio packages list --check # Also fail if packages unhealthy[/code]
231"""
233check_option = click.option(
234 "check", # Var name.
235 "-c",
236 "--check",
237 is_flag=True,
238 help="Error on unhealthy packages.",
239 cls=ApioOption,
240)
243@click.command(
244 name="list",
245 cls=ApioCommand,
246 short_help="List apio packages.",
247 help=APIO_PACKAGES_LIST_HELP,
248)
249@check_option
250def _list_cli(check: bool):
251 """Implements the 'apio packages list' command."""
253 # -- It's important that will command will use IGNORE_PACKAGES so we
254 # -- can list the current state of packages without mutating it.
255 apio_ctx = ApioContext(
256 project_policy=ProjectPolicy.NO_PROJECT,
257 remote_config_policy=RemoteConfigPolicy.GET_FRESH,
258 packages_policy=PackagesPolicy.IGNORE_PACKAGES,
259 )
261 # -- Print packages report.
262 packages_ok = print_packages_report2(apio_ctx)
264 # -- Handle check failure
265 if check and not packages_ok: 265 ↛ 266line 265 didn't jump to line 266 because the condition on line 265 was never true
266 fatal_error("Packages are unhealthy. (--check failed)")
268 # All done OK
271# ------ apio packages (group)
273# -- Text in the rich-text format of the python rich library.
274APIO_PACKAGES_HELP = """
275The command group 'apio packages' provides commands to manage the \
276installation of Apio packages. These are not Python packages but \
277Apio packages containing various tools and data essential for the \
278operation of Apio.
280The list of available packages depends on the operating system you are \
281using and may vary between different operating systems.
282"""
285# -- We have only a single group with the title 'Subcommands'.
286SUBGROUPS = [
287 ApioSubgroup(
288 "Subcommands",
289 [
290 _install_cli,
291 _list_cli,
292 ],
293 )
294]
297@click.command(
298 name="packages",
299 cls=ApioGroup,
300 subgroups=SUBGROUPS,
301 short_help="Manage the apio packages.",
302 help=APIO_PACKAGES_HELP,
303)
304def cli():
305 """Implements the 'apio packages' command group.'"""
307 # pass