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