Coverage for apio / commands / apio_packages.py: 77%
91 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 packages' command"""
10import sys
11from typing import Dict
12from dataclasses import dataclass
13import click
14from rich.table import Table
15from rich import box
16from apio.common.apio_console import cout, ctable, cerror
17from apio.common.apio_styles import INFO, BORDER, ERROR, SUCCESS
18from apio.managers import packages
19from apio.commands import options
20from apio.utils.cmd_util import ApioGroup, ApioSubgroup, ApioCommand
21from apio.apio_context import (
22 ApioContext,
23 ProjectPolicy,
24 RemoteConfigPolicy,
25 PackagesPolicy,
26)
29@dataclass(frozen=True)
30class RequiredPackageRow:
31 """Information of a row of a required package."""
33 # -- Package name
34 name: str
35 # -- The status column text value.
36 status: str
37 # -- The style to use for the row.
38 style: str
41def print_packages_report(apio_ctx: ApioContext) -> bool:
42 """A common function to print the state of the packages.
43 Returns True if the packages are OK.
44 """
46 # -- Scan the packages
47 scan = packages.scan_packages(apio_ctx.packages_context)
49 # -- Shortcuts to reduce clutter.
50 get_installed_package_info = apio_ctx.profile.get_installed_package_info
51 get_required_package_info = apio_ctx.get_required_package_info
53 table = Table(
54 show_header=True,
55 show_lines=True,
56 box=box.SQUARE,
57 border_style=BORDER,
58 title="Apio Packages Status",
59 title_justify="left",
60 padding=(0, 2),
61 )
63 table.add_column("PACKAGE NAME", no_wrap=True)
64 table.add_column("VERSION", no_wrap=True)
65 table.add_column("PLATFORM", no_wrap=True)
66 table.add_column("DESCRIPTION", no_wrap=True)
67 table.add_column("STATUS", no_wrap=True)
69 required_packages_rows: Dict[RequiredPackageRow] = {}
71 # -- Collect rows of required packages that are installed OK.
72 for package_name in scan.installed_ok_package_names:
73 assert package_name not in required_packages_rows
74 required_packages_rows[package_name] = RequiredPackageRow(
75 package_name, "OK", None
76 )
78 # -- Collect rows of required packages that are uninstalled.
79 for package_name in scan.uninstalled_package_names: 79 ↛ 80line 79 didn't jump to line 80 because the loop on line 79 never started
80 assert package_name not in required_packages_rows
81 required_packages_rows[package_name] = RequiredPackageRow(
82 package_name, "Uninstalled", INFO
83 )
85 # -- Collect rows of required packages have version or platform mismatch.
86 for package_name in scan.bad_version_package_names: 86 ↛ 87line 86 didn't jump to line 87 because the loop on line 86 never started
87 assert package_name not in required_packages_rows
88 required_packages_rows[package_name] = RequiredPackageRow(
89 package_name, "Mismatch", ERROR
90 )
92 # -- Collect rows of required packages that are broken.
93 for package_name in scan.broken_package_names: 93 ↛ 94line 93 didn't jump to line 94 because the loop on line 93 never started
94 assert package_name not in required_packages_rows
95 required_packages_rows[package_name] = RequiredPackageRow(
96 package_name, "Broken", ERROR
97 )
99 # -- Add the required packages rows to the table, in the order that they
100 # -- are statically defined in the remote config file.
101 assert set(required_packages_rows.keys()) == (
102 apio_ctx.required_packages.keys()
103 )
104 for package_name in apio_ctx.required_packages:
105 row_info = required_packages_rows[package_name]
106 version, platform_id = get_installed_package_info(package_name)
107 description = get_required_package_info(package_name)["description"]
108 table.add_row(
109 package_name,
110 version,
111 platform_id,
112 description,
113 row_info.status,
114 style=row_info.style,
115 )
117 # -- Render table.
118 cout()
119 ctable(table)
121 # -- Define errors table.
122 table = Table(
123 show_header=True,
124 show_lines=True,
125 box=box.SQUARE,
126 border_style=BORDER,
127 title="Apio Packages Errors",
128 title_justify="left",
129 padding=(0, 2),
130 )
132 # -- Add columns.
133 table.add_column("ERROR TYPE", no_wrap=True, min_width=15, style=ERROR)
134 table.add_column("NAME", no_wrap=True, min_width=15)
136 # -- Add rows.
137 for package_name in scan.orphan_package_names: 137 ↛ 138line 137 didn't jump to line 138 because the loop on line 137 never started
138 table.add_row("Orphan package", package_name)
140 for name in sorted(scan.orphan_dir_names): 140 ↛ 141line 140 didn't jump to line 141 because the loop on line 140 never started
141 table.add_row("Orphan dir", name)
143 for name in sorted(scan.orphan_file_names): 143 ↛ 144line 143 didn't jump to line 144 because the loop on line 143 never started
144 table.add_row("Orphan file", name)
146 # -- Render the table, unless empty.
147 if table.row_count: 147 ↛ 148line 147 didn't jump to line 148 because the condition on line 147 was never true
148 cout()
149 ctable(table)
151 # -- Scan packages again and print a summary.
152 packages_ok = scan.is_all_ok()
154 cout()
155 if packages_ok: 155 ↛ 158line 155 didn't jump to line 158 because the condition on line 155 was always true
156 cout("All Apio packages are installed OK.", style=SUCCESS)
157 else:
158 cout(
159 "Run 'apio packages update' to update the packages.",
160 style=INFO,
161 )
163 # -- Return with the current packages status. Normally it should be
164 # -- True for OK since we fixed and updated the packages.
165 return packages_ok
168# ------ apio packages update
170# -- Text in the rich-text format of the python rich library.
171APIO_PACKAGES_UPDATE_HELP = """
172The command 'apio packages update' updates the installed Apio packages \
173to their latest requirements.
175Examples:[code]
176 apio packages update # Update packages
177 apio pack upd # Same, with shortcuts
178 apio packages update --force # Force reinstallation from scratch
179 apio packages update --verbose # Provide additional info[/code]
181Adding the '--force' option forces the reinstallation of existing packages; \
182otherwise, packages that are already installed correctly remain unchanged.
184It is highly recommended to run the 'apio packages update' once in a while \
185because it check the Apio remote server for the latest packages versions \
186which may included fixes and enhancements such as new examples that were \
187added to the examples package.
188"""
191@click.command(
192 name="update",
193 cls=ApioCommand,
194 short_help="Update apio packages.",
195 help=APIO_PACKAGES_UPDATE_HELP,
196)
197@options.force_option_gen(short_help="Force reinstallation.")
198@options.verbose_option
199def _update_cli(
200 # Options
201 force: bool,
202 verbose: bool,
203):
204 """Implements the 'apio packages update' command."""
206 apio_ctx = ApioContext(
207 project_policy=ProjectPolicy.NO_PROJECT,
208 remote_config_policy=RemoteConfigPolicy.GET_FRESH,
209 packages_policy=PackagesPolicy.IGNORE_PACKAGES,
210 )
212 # cout(f"Platform id '{apio_ctx.platform_id}'")
214 # -- First thing, fix broken packages, if any. This forces fetching
215 # -- of the latest remote config file.
216 packages.scan_and_fix_packages(apio_ctx.packages_context)
218 # -- Install the packages, one by one.
219 for package in apio_ctx.required_packages:
220 packages.install_package(
221 apio_ctx.packages_context,
222 package_name=package,
223 force_reinstall=force,
224 verbose=verbose,
225 )
227 # -- If verbose, print a full report.
228 if verbose: 228 ↛ 229line 228 didn't jump to line 229 because the condition on line 228 was never true
229 package_ok = print_packages_report(apio_ctx)
230 sys.exit(0 if package_ok else 1)
232 # -- When not in verbose mode, we run a scan and print a short status.
233 scan = packages.scan_packages(apio_ctx.packages_context)
234 if not scan.is_all_ok(): 234 ↛ 235line 234 didn't jump to line 235 because the condition on line 234 was never true
235 cerror("Failed to update some packages.")
236 cout(
237 "Run 'apio packages list' to view the packages.",
238 style=INFO,
239 )
240 sys.exit(1)
242 # -- Here when packages are ok.
243 cout("All Apio packages are installed OK.", style=SUCCESS)
246# ------ apio packages list
248# -- Text in the rich-text format of the python rich library.
249APIO_PACKAGES_LIST_HELP = """
250The command 'apio packages list' lists the available and installed Apio \
251packages. The list of available packages depends on the operating system \
252you are using and may vary between operating systems.
254Examples:[code]
255 apio packages list[/code]
256"""
259@click.command(
260 name="list",
261 cls=ApioCommand,
262 short_help="List apio packages.",
263 help=APIO_PACKAGES_LIST_HELP,
264)
265# @options.verbose_option
266def _list_cli():
267 """Implements the 'apio packages list' command."""
269 apio_ctx = ApioContext(
270 project_policy=ProjectPolicy.NO_PROJECT,
271 remote_config_policy=RemoteConfigPolicy.GET_FRESH,
272 packages_policy=PackagesPolicy.IGNORE_PACKAGES,
273 )
275 # -- Print packages report.
276 packages_ok = print_packages_report(apio_ctx)
277 sys.exit(0 if packages_ok else 1)
280# ------ apio packages (group)
282# -- Text in the rich-text format of the python rich library.
283APIO_PACKAGES_HELP = """
284The command group 'apio packages' provides commands to manage the \
285installation of Apio packages. These are not Python packages but \
286Apio packages containing various tools and data essential for the \
287operation of Apio.
289The list of available packages depends on the operating system you are \
290using and may vary between different operating systems.
291"""
294# -- We have only a single group with the title 'Subcommands'.
295SUBGROUPS = [
296 ApioSubgroup(
297 "Subcommands",
298 [
299 _update_cli,
300 _list_cli,
301 ],
302 )
303]
306@click.command(
307 name="packages",
308 cls=ApioGroup,
309 subgroups=SUBGROUPS,
310 short_help="Manage the apio packages.",
311 help=APIO_PACKAGES_HELP,
312)
313def cli():
314 """Implements the 'apio packages' command group.'"""
316 # pass