Coverage for apio/commands/apio_packages.py: 77%
92 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-06-24 03:51 +0000
« prev ^ index » next coverage.py v7.14.3, created at 2026-06-24 03:51 +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 | None
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[str, 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 info = get_required_package_info(package_name)
108 description = info["description"]
109 table.add_row(
110 package_name,
111 version,
112 platform_id,
113 description,
114 row_info.status,
115 style=row_info.style,
116 )
118 # -- Render table.
119 cout()
120 ctable(table)
122 # -- Define errors table.
123 table = Table(
124 show_header=True,
125 show_lines=True,
126 box=box.SQUARE,
127 border_style=BORDER,
128 title="Apio Packages Errors",
129 title_justify="left",
130 padding=(0, 2),
131 )
133 # -- Add columns.
134 table.add_column("ERROR TYPE", no_wrap=True, min_width=15, style=ERROR)
135 table.add_column("NAME", no_wrap=True, min_width=15)
137 # -- Add rows.
138 for package_name in scan.orphan_package_names: 138 ↛ 139line 138 didn't jump to line 139 because the loop on line 138 never started
139 table.add_row("Orphan package", package_name)
141 for name in sorted(scan.orphan_dir_names): 141 ↛ 142line 141 didn't jump to line 142 because the loop on line 141 never started
142 table.add_row("Orphan dir", name)
144 for name in sorted(scan.orphan_file_names): 144 ↛ 145line 144 didn't jump to line 145 because the loop on line 144 never started
145 table.add_row("Orphan file", name)
147 # -- Render the table, unless empty.
148 if table.row_count: 148 ↛ 149line 148 didn't jump to line 149 because the condition on line 148 was never true
149 cout()
150 ctable(table)
152 # -- Scan packages again and print a summary.
153 packages_ok = scan.is_all_ok()
155 cout()
156 if packages_ok: 156 ↛ 159line 156 didn't jump to line 159 because the condition on line 156 was always true
157 cout("All Apio packages are installed OK.", style=SUCCESS)
158 else:
159 cout(
160 "Run 'apio packages install' to install the packages.",
161 style=INFO,
162 )
164 # -- Return with the current packages status. Normally it should be
165 # -- True for OK since we fixed and installed the packages.
166 return packages_ok
169# ------ apio packages install
171# -- Text in the rich-text format of the python rich library.
172APIO_PACKAGES_INSTALL_HELP = """
173The command 'apio packages install' installs the installed Apio packages \
174to their latest requirements.
176Examples:[code]
177 apio packages install # Install packages
178 apio pack upd # Same, with shortcuts
179 apio packages install --force # Force reinstallation from scratch
180 apio packages install --verbose # Provide additional info[/code]
182Adding the '--force' option forces the reinstallation of existing packages; \
183otherwise, packages that are already installed correctly remain unchanged.
185It is highly recommended to run the 'apio packages install' once in a while \
186because it check the Apio remote server for the latest packages versions \
187which may included fixes and enhancements such as new examples that were \
188added to the examples package.
189"""
192@click.command(
193 name="install",
194 cls=ApioCommand,
195 short_help="Install apio packages.",
196 help=APIO_PACKAGES_INSTALL_HELP,
197)
198@options.force_option_gen(short_help="Force reinstallation.")
199@options.verbose_option
200def _install_cli(
201 *,
202 # Options
203 force: bool,
204 verbose: bool,
205):
206 """Implements the 'apio packages install' command."""
208 apio_ctx = ApioContext(
209 project_policy=ProjectPolicy.NO_PROJECT,
210 remote_config_policy=RemoteConfigPolicy.GET_FRESH,
211 packages_policy=PackagesPolicy.IGNORE_PACKAGES,
212 )
214 # cout(f"Platform id '{apio_ctx.platform_id}'")
216 # -- First thing, fix broken packages, if any. This forces fetching
217 # -- of the latest remote config file.
218 packages.scan_and_fix_packages(apio_ctx.packages_context)
220 # -- Install the packages, one by one.
221 for package in apio_ctx.required_packages:
222 packages.install_package(
223 apio_ctx.packages_context,
224 package_name=package,
225 force_reinstall=force,
226 verbose=verbose,
227 )
229 # -- If verbose, print a full report.
230 if verbose: 230 ↛ 231line 230 didn't jump to line 231 because the condition on line 230 was never true
231 package_ok = print_packages_report(apio_ctx)
232 sys.exit(0 if package_ok else 1)
234 # -- When not in verbose mode, we run a scan and print a short status.
235 scan = packages.scan_packages(apio_ctx.packages_context)
236 if not scan.is_all_ok(): 236 ↛ 237line 236 didn't jump to line 237 because the condition on line 236 was never true
237 cerror("Failed to install some packages.")
238 cout(
239 "Run 'apio packages list' to view the packages.",
240 style=INFO,
241 )
242 sys.exit(1)
244 # -- Here when packages are ok.
245 cout("All Apio packages are installed OK.", style=SUCCESS)
248# ------ apio packages list
250# -- Text in the rich-text format of the python rich library.
251APIO_PACKAGES_LIST_HELP = """
252The command 'apio packages list' lists the available and installed Apio \
253packages. The list of available packages depends on the operating system \
254you are using and may vary between operating systems.
256Examples:[code]
257 apio packages list[/code]
258"""
261@click.command(
262 name="list",
263 cls=ApioCommand,
264 short_help="List apio packages.",
265 help=APIO_PACKAGES_LIST_HELP,
266)
267# @options.verbose_option
268def _list_cli():
269 """Implements the 'apio packages list' command."""
271 apio_ctx = ApioContext(
272 project_policy=ProjectPolicy.NO_PROJECT,
273 remote_config_policy=RemoteConfigPolicy.GET_FRESH,
274 packages_policy=PackagesPolicy.IGNORE_PACKAGES,
275 )
277 # -- Print packages report.
278 packages_ok = print_packages_report(apio_ctx)
279 sys.exit(0 if packages_ok else 1)
282# ------ apio packages (group)
284# -- Text in the rich-text format of the python rich library.
285APIO_PACKAGES_HELP = """
286The command group 'apio packages' provides commands to manage the \
287installation of Apio packages. These are not Python packages but \
288Apio packages containing various tools and data essential for the \
289operation of Apio.
291The list of available packages depends on the operating system you are \
292using and may vary between different operating systems.
293"""
296# -- We have only a single group with the title 'Subcommands'.
297SUBGROUPS = [
298 ApioSubgroup(
299 "Subcommands",
300 [
301 _install_cli,
302 _list_cli,
303 ],
304 )
305]
308@click.command(
309 name="packages",
310 cls=ApioGroup,
311 subgroups=SUBGROUPS,
312 short_help="Manage the apio packages.",
313 help=APIO_PACKAGES_HELP,
314)
315def cli():
316 """Implements the 'apio packages' command group.'"""
318 # pass