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

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""" 

9 

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) 

26 

27 

28@dataclass(frozen=True) 

29class RequiredPackageRow: 

30 """Information of a row of a required package.""" 

31 

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 

38 

39 

40def print_packages_report(apio_ctx: ApioContext) -> None: 

41 """A common function to print the state of the packages.""" 

42 

43 # -- Scan the packages 

44 scan = packages.scan_packages(apio_ctx.packages_context) 

45 

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 

49 

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 ) 

59 

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) 

65 

66 required_packages_rows: Dict[RequiredPackageRow] = {} 

67 

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 ) 

74 

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 ) 

81 

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 ) 

88 

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 ) 

95 

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 ) 

113 

114 # -- Render table. 

115 cout() 

116 ctable(table) 

117 

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 ) 

128 

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) 

132 

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) 

136 

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) 

139 

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) 

142 

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) 

147 

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 ) 

157 

158 

159# ------ apio packages update 

160 

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. 

165 

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] 

171 

172Adding the '--force' option forces the reinstallation of existing packages; \ 

173otherwise, packages that are already installed correctly remain unchanged. 

174 

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""" 

180 

181 

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.""" 

196 

197 apio_ctx = ApioContext( 

198 project_policy=ProjectPolicy.NO_PROJECT, 

199 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

200 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

201 ) 

202 

203 # cout(f"Platform id '{apio_ctx.platform_id}'") 

204 

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) 

208 

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 ) 

217 

218 # -- Scan the available and installed packages. 

219 print_packages_report(apio_ctx) 

220 

221 

222# ------ apio packages list 

223 

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. 

229 

230Examples:[code] 

231 apio packages list[/code] 

232""" 

233 

234 

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.""" 

244 

245 apio_ctx = ApioContext( 

246 project_policy=ProjectPolicy.NO_PROJECT, 

247 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

248 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

249 ) 

250 

251 # -- Print packages report. 

252 print_packages_report(apio_ctx) 

253 

254 

255# ------ apio packages (group) 

256 

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. 

263 

264The list of available packages depends on the operating system you are \ 

265using and may vary between different operating systems. 

266""" 

267 

268 

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] 

279 

280 

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.'""" 

290 

291 # pass