Coverage for apio/commands/apio_packages.py: 80%
79 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-06 10:20 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-06 10:20 +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"""
10from typing import Dict
11from dataclasses import dataclass
12import click
13from rich.table import Table
14from rich import box
15from apio.common.apio_console import cout, ctable
16from apio.common.apio_styles import INFO, BORDER, ERROR, SUCCESS
17from apio.managers import packages
18from apio.commands import options
19from apio.utils.cmd_util import ApioGroup, ApioSubgroup, ApioCommand
20from apio.apio_context import (
21 ApioContext,
22 ProjectPolicy,
23 RemoteConfigPolicy,
24 PackagesPolicy,
25)
28@dataclass(frozen=True)
29class RequiredPackageRow:
30 """Information of a row of a required package."""
32 # -- Package name
33 name: str
34 # -- The status column text value.
35 status: str
36 # -- The style to use for the row.
37 style: str
40def print_packages_report(apio_ctx: ApioContext) -> None:
41 """A common function to print the state of the packages."""
43 # -- Scan the packages
44 scan = packages.scan_packages(apio_ctx.packages_context)
46 # -- Shortcuts to reduce clutter.
47 get_installed_package_info = apio_ctx.profile.get_installed_package_info
48 get_required_package_info = apio_ctx.get_required_package_info
50 table = Table(
51 show_header=True,
52 show_lines=True,
53 box=box.SQUARE,
54 border_style=BORDER,
55 title="Apio Packages Status",
56 title_justify="left",
57 padding=(0, 2),
58 )
60 table.add_column("PACKAGE NAME", no_wrap=True)
61 table.add_column("VERSION", no_wrap=True)
62 table.add_column("PLATFORM", no_wrap=True)
63 table.add_column("DESCRIPTION", no_wrap=True)
64 table.add_column("STATUS", no_wrap=True)
66 required_packages_rows: Dict[RequiredPackageRow] = {}
68 # -- Collect rows of required packages that are installed OK.
69 for package_name in scan.installed_ok_package_names:
70 assert package_name not in required_packages_rows
71 required_packages_rows[package_name] = RequiredPackageRow(
72 package_name, "OK", None
73 )
75 # -- Collect rows of required packages that are uninstalled.
76 for package_name in scan.uninstalled_package_names: 76 ↛ 77line 76 didn't jump to line 77 because the loop on line 76 never started
77 assert package_name not in required_packages_rows
78 required_packages_rows[package_name] = RequiredPackageRow(
79 package_name, "Uninstalled", INFO
80 )
82 # -- Collect rows of required packages have version or platform mismatch.
83 for package_name in scan.bad_version_package_names: 83 ↛ 84line 83 didn't jump to line 84 because the loop on line 83 never started
84 assert package_name not in required_packages_rows
85 required_packages_rows[package_name] = RequiredPackageRow(
86 package_name, "Mismatch", ERROR
87 )
89 # -- Collect rows of required packages that are broken.
90 for package_name in scan.broken_package_names: 90 ↛ 91line 90 didn't jump to line 91 because the loop on line 90 never started
91 assert package_name not in required_packages_rows
92 required_packages_rows[package_name] = RequiredPackageRow(
93 package_name, "Broken", ERROR
94 )
96 # -- Add the required packages rows to the table, in the order that they
97 # -- are statically defined in the remote config file.
98 assert set(required_packages_rows.keys()) == (
99 apio_ctx.required_packages.keys()
100 )
101 for package_name in apio_ctx.required_packages:
102 row_info = required_packages_rows[package_name]
103 version, platform_id = get_installed_package_info(package_name)
104 description = get_required_package_info(package_name)["description"]
105 table.add_row(
106 package_name,
107 version,
108 platform_id,
109 description,
110 row_info.status,
111 style=row_info.style,
112 )
114 # -- Render table.
115 cout()
116 ctable(table)
118 # -- Define errors table.
119 table = Table(
120 show_header=True,
121 show_lines=True,
122 box=box.SQUARE,
123 border_style=BORDER,
124 title="Apio Packages Errors",
125 title_justify="left",
126 padding=(0, 2),
127 )
129 # -- Add columns.
130 table.add_column("ERROR TYPE", no_wrap=True, min_width=15, style=ERROR)
131 table.add_column("NAME", no_wrap=True, min_width=15)
133 # -- Add rows.
134 for package_name in scan.orphan_package_names: 134 ↛ 135line 134 didn't jump to line 135 because the loop on line 134 never started
135 table.add_row("Orphan package", package_name)
137 for name in sorted(scan.orphan_dir_names): 137 ↛ 138line 137 didn't jump to line 138 because the loop on line 137 never started
138 table.add_row("Orphan dir", name)
140 for name in sorted(scan.orphan_file_names): 140 ↛ 141line 140 didn't jump to line 141 because the loop on line 140 never started
141 table.add_row("Orphan file", name)
143 # -- Render the table, unless empty.
144 if table.row_count: 144 ↛ 145line 144 didn't jump to line 145 because the condition on line 144 was never true
145 cout()
146 ctable(table)
148 # -- Print summary.
149 cout()
150 if scan.is_all_ok(): 150 ↛ 153line 150 didn't jump to line 153 because the condition on line 150 was always true
151 cout("All Apio packages are installed OK.", style=SUCCESS)
152 else:
153 cout(
154 "Run 'apio packages update' to update the packages.",
155 style=INFO,
156 )
159# ------ apio packages update
161# -- Text in the rich-text format of the python rich library.
162APIO_PACKAGES_UPDATE_HELP = """
163The command 'apio packages update' updates the installed Apio packages \
164to their latest requirements.
166Examples:[code]
167 apio packages update # Update packages
168 apio pack upd # Same, with shortcuts
169 apio packages update --force # Force reinstallation from scratch
170 apio packages update --verbose # Provide additional info[/code]
172Adding the '--force' option forces the reinstallation of existing packages; \
173otherwise, packages that are already installed correctly remain unchanged.
175It is highly recommended to run the 'apio packages update' once in a while \
176because it check the Apio remote server for the latest packages versions \
177which may included fixes and enhancements such as new examples that were \
178added to the examples package.
179"""
182@click.command(
183 name="update",
184 cls=ApioCommand,
185 short_help="Update apio packages.",
186 help=APIO_PACKAGES_UPDATE_HELP,
187)
188@options.force_option_gen(short_help="Force reinstallation.")
189@options.verbose_option
190def _update_cli(
191 # Options
192 force: bool,
193 verbose: bool,
194):
195 """Implements the 'apio packages update' command."""
197 apio_ctx = ApioContext(
198 project_policy=ProjectPolicy.NO_PROJECT,
199 remote_config_policy=RemoteConfigPolicy.GET_FRESH,
200 packages_policy=PackagesPolicy.IGNORE_PACKAGES,
201 )
203 # cout(f"Platform id '{apio_ctx.platform_id}'")
205 # -- First thing, fix broken packages, if any. This forces fetching
206 # -- of the latest remote config file.
207 packages.scan_and_fix_packages(apio_ctx.packages_context)
209 # -- Install the packages, one by one.
210 for package in apio_ctx.required_packages:
211 packages.install_package(
212 apio_ctx.packages_context,
213 package_name=package,
214 force_reinstall=force,
215 verbose=verbose,
216 )
218 # -- Scan the available and installed packages.
219 print_packages_report(apio_ctx)
222# ------ apio packages list
224# -- Text in the rich-text format of the python rich library.
225APIO_PACKAGES_LIST_HELP = """
226The command 'apio packages list' lists the available and installed Apio \
227packages. The list of available packages depends on the operating system \
228you are using and may vary between operating systems.
230Examples:[code]
231 apio packages list[/code]
232"""
235@click.command(
236 name="list",
237 cls=ApioCommand,
238 short_help="List apio packages.",
239 help=APIO_PACKAGES_LIST_HELP,
240)
241# @options.verbose_option
242def _list_cli():
243 """Implements the 'apio packages list' command."""
245 apio_ctx = ApioContext(
246 project_policy=ProjectPolicy.NO_PROJECT,
247 remote_config_policy=RemoteConfigPolicy.GET_FRESH,
248 packages_policy=PackagesPolicy.IGNORE_PACKAGES,
249 )
251 # -- Print packages report.
252 print_packages_report(apio_ctx)
255# ------ apio packages (group)
257# -- Text in the rich-text format of the python rich library.
258APIO_PACKAGES_HELP = """
259The command group 'apio packages' provides commands to manage the \
260installation of Apio packages. These are not Python packages but \
261Apio packages containing various tools and data essential for the \
262operation of Apio.
264The list of available packages depends on the operating system you are \
265using and may vary between different operating systems.
266"""
269# -- We have only a single group with the title 'Subcommands'.
270SUBGROUPS = [
271 ApioSubgroup(
272 "Subcommands",
273 [
274 _update_cli,
275 _list_cli,
276 ],
277 )
278]
281@click.command(
282 name="packages",
283 cls=ApioGroup,
284 subgroups=SUBGROUPS,
285 short_help="Manage the apio packages.",
286 help=APIO_PACKAGES_HELP,
287)
288def cli():
289 """Implements the 'apio packages' command group.'"""
291 # pass