Coverage for apio / commands / apio_packages.py: 77%
91 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-26 02:38 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-26 02:38 +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 install' to install 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 installed the packages.
165 return packages_ok
168# ------ apio packages install
170# -- Text in the rich-text format of the python rich library.
171APIO_PACKAGES_INSTALL_HELP = """
172The command 'apio packages install' installs the installed Apio packages \
173to their latest requirements.
175Examples:[code]
176 apio packages install # Install packages
177 apio pack upd # Same, with shortcuts
178 apio packages install --force # Force reinstallation from scratch
179 apio packages install --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 install' 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="install",
193 cls=ApioCommand,
194 short_help="Install apio packages.",
195 help=APIO_PACKAGES_INSTALL_HELP,
196)
197@options.force_option_gen(short_help="Force reinstallation.")
198@options.verbose_option
199def _install_cli(
200 *,
201 # Options
202 force: bool,
203 verbose: bool,
204):
205 """Implements the 'apio packages install' command."""
207 apio_ctx = ApioContext(
208 project_policy=ProjectPolicy.NO_PROJECT,
209 remote_config_policy=RemoteConfigPolicy.GET_FRESH,
210 packages_policy=PackagesPolicy.IGNORE_PACKAGES,
211 )
213 # cout(f"Platform id '{apio_ctx.platform_id}'")
215 # -- First thing, fix broken packages, if any. This forces fetching
216 # -- of the latest remote config file.
217 packages.scan_and_fix_packages(apio_ctx.packages_context)
219 # -- Install the packages, one by one.
220 for package in apio_ctx.required_packages:
221 packages.install_package(
222 apio_ctx.packages_context,
223 package_name=package,
224 force_reinstall=force,
225 verbose=verbose,
226 )
228 # -- If verbose, print a full report.
229 if verbose: 229 ↛ 230line 229 didn't jump to line 230 because the condition on line 229 was never true
230 package_ok = print_packages_report(apio_ctx)
231 sys.exit(0 if package_ok else 1)
233 # -- When not in verbose mode, we run a scan and print a short status.
234 scan = packages.scan_packages(apio_ctx.packages_context)
235 if not scan.is_all_ok(): 235 ↛ 236line 235 didn't jump to line 236 because the condition on line 235 was never true
236 cerror("Failed to install some packages.")
237 cout(
238 "Run 'apio packages list' to view the packages.",
239 style=INFO,
240 )
241 sys.exit(1)
243 # -- Here when packages are ok.
244 cout("All Apio packages are installed OK.", style=SUCCESS)
247# ------ apio packages list
249# -- Text in the rich-text format of the python rich library.
250APIO_PACKAGES_LIST_HELP = """
251The command 'apio packages list' lists the available and installed Apio \
252packages. The list of available packages depends on the operating system \
253you are using and may vary between operating systems.
255Examples:[code]
256 apio packages list[/code]
257"""
260@click.command(
261 name="list",
262 cls=ApioCommand,
263 short_help="List apio packages.",
264 help=APIO_PACKAGES_LIST_HELP,
265)
266# @options.verbose_option
267def _list_cli():
268 """Implements the 'apio packages list' command."""
270 apio_ctx = ApioContext(
271 project_policy=ProjectPolicy.NO_PROJECT,
272 remote_config_policy=RemoteConfigPolicy.GET_FRESH,
273 packages_policy=PackagesPolicy.IGNORE_PACKAGES,
274 )
276 # -- Print packages report.
277 packages_ok = print_packages_report(apio_ctx)
278 sys.exit(0 if packages_ok else 1)
281# ------ apio packages (group)
283# -- Text in the rich-text format of the python rich library.
284APIO_PACKAGES_HELP = """
285The command group 'apio packages' provides commands to manage the \
286installation of Apio packages. These are not Python packages but \
287Apio packages containing various tools and data essential for the \
288operation of Apio.
290The list of available packages depends on the operating system you are \
291using and may vary between different operating systems.
292"""
295# -- We have only a single group with the title 'Subcommands'.
296SUBGROUPS = [
297 ApioSubgroup(
298 "Subcommands",
299 [
300 _install_cli,
301 _list_cli,
302 ],
303 )
304]
307@click.command(
308 name="packages",
309 cls=ApioGroup,
310 subgroups=SUBGROUPS,
311 short_help="Manage the apio packages.",
312 help=APIO_PACKAGES_HELP,
313)
314def cli():
315 """Implements the 'apio packages' command group.'"""
317 # pass