Coverage for apio/commands/apio_packages.py: 81%

70 statements  

« prev     ^ index     » next       coverage.py v7.17.2, created at 2026-11-07 05:08 +0000

1# This file is part of the Apio project 

2# (C) 2016-present FPGAwars 

3# Project authors: 

4# * Jesús Arroyo (Jesus89) (2016-2019) 

5# * Juan Gonzalez (obijuan) (2019-2024) 

6# * The Apio Team (2024-present) 

7# License: GPLv2 

8 

9"""Implementation of 'apio packages' command""" 

10 

11import sys 

12import click 

13from rich.table import Table 

14from rich import box 

15from apio.common.apio_console import cout, ctable, fatal_error 

16from apio.common.apio_styles import INFO, BORDER, ERROR, SUCCESS 

17from apio.commands import options 

18from apio.managers.package_manager import ( 

19 PackagesScanResults, 

20) 

21from apio.utils.cmd_util import ( 

22 ApioGroup, 

23 ApioSubgroup, 

24 ApioCommand, 

25 ApioOption, 

26) 

27from apio.apio_context import ( 

28 ApioContext, 

29 ProjectPolicy, 

30 RemoteConfigPolicy, 

31 PackagesPolicy, 

32) 

33 

34 

35def print_packages_report2(apio_ctx: ApioContext) -> bool: 

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

37 Returns True if the packages are OK. 

38 """ 

39 

40 # -- Scan the packages 

41 # scan = packages.scan_packages(apio_ctx.package_manager) 

42 scan: PackagesScanResults = apio_ctx.package_manager.scan_packages() 

43 

44 # ===== Required Packages Table ===== 

45 

46 table = Table( 

47 show_header=True, 

48 show_lines=True, 

49 box=box.SQUARE, 

50 border_style=BORDER, 

51 title="Apio Packages Status", 

52 title_justify="left", 

53 padding=(0, 2), 

54 ) 

55 

56 table.add_column("PACKAGE NAME", no_wrap=True) 

57 table.add_column("VERSION", no_wrap=True) 

58 # table.add_column("PLATFORM", no_wrap=True) 

59 table.add_column("DESCRIPTION", no_wrap=True) 

60 table.add_column("STATUS", no_wrap=True) 

61 

62 for package_name, package_status in scan.platform_packages.items(): 

63 

64 # -- Collect additional info about the package. 

65 package_manager = apio_ctx.package_manager 

66 installed_version, *_ = package_manager.get_installed_package_info( 

67 package_name 

68 ) 

69 package_spec = package_manager.get_required_package_spec(package_name) 

70 

71 # -- Determine row color 

72 row_style = ( 

73 ERROR 

74 if package_status.is_inconsistency 

75 else INFO if not package_status.is_ok else None 

76 ) 

77 

78 # -- Add a table row for the package. 

79 table.add_row( 

80 package_name, 

81 installed_version, 

82 # package_info["description"], 

83 package_spec.description, 

84 package_status.value, 

85 style=row_style, 

86 ) 

87 

88 # -- Render table. 

89 cout() 

90 ctable(table) 

91 

92 # ===== Orphans Table ===== 

93 

94 # -- Define errors table. 

95 table = Table( 

96 show_header=True, 

97 show_lines=True, 

98 box=box.SQUARE, 

99 border_style=BORDER, 

100 title="Apio Packages Errors", 

101 title_justify="left", 

102 padding=(0, 2), 

103 ) 

104 

105 # -- Add columns. 

106 table.add_column("ERROR TYPE", no_wrap=True, min_width=15, style=ERROR) 

107 table.add_column("NAME", no_wrap=True, min_width=15) 

108 

109 # -- Add rows. 

110 for orphan_name, orphan_type in scan.orphans.items(): 110 ↛ 111line 110 didn't jump to line 111 because the loop on line 110 never started

111 table.add_row(orphan_name, orphan_type.value) 

112 

113 # -- Render the table, unless empty. 

114 if table.row_count: 114 ↛ 115line 114 didn't jump to line 115 because the condition on line 114 was never true

115 cout() 

116 ctable(table) 

117 

118 # -- Scan packages again and print a summary. 

119 packages_ok = scan.is_all_ok() 

120 

121 cout() 

122 if packages_ok: 122 ↛ 125line 122 didn't jump to line 125 because the condition on line 122 was always true

123 cout("All Apio packages are installed OK.", style=SUCCESS) 

124 else: 

125 cout( 

126 "Run 'apio packages install' to install the packages.", 

127 style=INFO, 

128 ) 

129 

130 # -- Return with the current packages status. Normally it should be 

131 # -- True for OK since we fixed and installed the packages. 

132 return packages_ok 

133 

134 

135# ------ apio packages install 

136 

137# -- Text in the rich-text format of the python rich library. 

138APIO_PACKAGES_INSTALL_HELP = """ 

139The command 'apio packages install' installs the installed Apio packages \ 

140to their latest requirements. 

141 

142Examples:[code] 

143 apio packages install # Install packages 

144 apio pack upd # Same, with shortcuts 

145 apio packages install --force # Force reinstallation from scratch 

146 apio packages install --verbose # Provide additional info[/code] 

147 

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

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

150 

151It is highly recommended to run the 'apio packages install' once in a while \ 

152because it check the Apio remote server for the latest packages versions \ 

153which may included fixes and enhancements such as new examples that were \ 

154added to the examples package. 

155""" 

156 

157 

158@click.command( 

159 name="install", 

160 cls=ApioCommand, 

161 short_help="Install apio packages.", 

162 help=APIO_PACKAGES_INSTALL_HELP, 

163) 

164@options.force_option_gen(short_help="Force reinstallation.") 

165@options.verbose_option 

166def _install_cli( 

167 *, 

168 # Options 

169 force: bool, 

170 verbose: bool, 

171): 

172 """Implements the 'apio packages install' command.""" 

173 

174 apio_ctx = ApioContext( 

175 project_policy=ProjectPolicy.NO_PROJECT, 

176 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

177 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

178 ) 

179 

180 # -- First thing, fix broken packages, if any. This forces fetching 

181 # -- of the latest remote config file. 

182 apio_ctx.package_manager.scan_and_fix_inconsistencies() 

183 

184 # -- Install the packages, one by one. 

185 for package in apio_ctx.platform_packages: 

186 apio_ctx.package_manager.install_package( 

187 package_name=package, 

188 force_reinstall=force, 

189 verbose=verbose, 

190 ) 

191 

192 # -- If verbose, print a full report. 

193 if verbose: 193 ↛ 194line 193 didn't jump to line 194 because the condition on line 193 was never true

194 package_ok = print_packages_report2(apio_ctx) 

195 if not package_ok: 

196 sys.exit(1) 

197 

198 # -- When not in verbose mode, we run a scan and print a short status. 

199 else: 

200 # scan = packages.scan_packages(apio_ctx.package_manager) 

201 scan = apio_ctx.package_manager.scan_packages() 

202 if not scan.is_all_ok(): 202 ↛ 203line 202 didn't jump to line 203 because the condition on line 202 was never true

203 fatal_error( 

204 "Failed to install some packages.", 

205 info="Run 'apio packages list' to view the packages.", 

206 ) 

207 

208 # -- In the verbose case above, this is already printed by 

209 # -- the print_packages_report() method. 

210 cout("All Apio packages are installed OK.", style=SUCCESS) 

211 

212 # -- We believe that we have the exactly the correct packages 

213 # -- installed. Perform a few final checks. 

214 apio_ctx.package_manager.check_packages_post_install() 

215 

216 

217# ------ apio packages list 

218 

219# -- Text in the rich-text format of the python rich library. 

220APIO_PACKAGES_LIST_HELP = """ 

221The command 'apio packages list' lists the available and installed Apio \ 

222packages. The list of available packages depends on the operating system \ 

223you are using and may vary between operating systems. 

224 

225The option '--check' causes the command to fail and exit with an error \ 

226status code if the packages are not installed properly. 

227 

228Examples:[code] 

229 apio packages list # Just report 

230 apio packages list --check # Also fail if packages unhealthy[/code] 

231""" 

232 

233check_option = click.option( 

234 "check", # Var name. 

235 "-c", 

236 "--check", 

237 is_flag=True, 

238 help="Error on unhealthy packages.", 

239 cls=ApioOption, 

240) 

241 

242 

243@click.command( 

244 name="list", 

245 cls=ApioCommand, 

246 short_help="List apio packages.", 

247 help=APIO_PACKAGES_LIST_HELP, 

248) 

249@check_option 

250def _list_cli(check: bool): 

251 """Implements the 'apio packages list' command.""" 

252 

253 # -- It's important that will command will use IGNORE_PACKAGES so we 

254 # -- can list the current state of packages without mutating it. 

255 apio_ctx = ApioContext( 

256 project_policy=ProjectPolicy.NO_PROJECT, 

257 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

258 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

259 ) 

260 

261 # -- Print packages report. 

262 packages_ok = print_packages_report2(apio_ctx) 

263 

264 # -- Handle check failure 

265 if check and not packages_ok: 265 ↛ 266line 265 didn't jump to line 266 because the condition on line 265 was never true

266 fatal_error("Packages are unhealthy. (--check failed)") 

267 

268 # All done OK 

269 

270 

271# ------ apio packages (group) 

272 

273# -- Text in the rich-text format of the python rich library. 

274APIO_PACKAGES_HELP = """ 

275The command group 'apio packages' provides commands to manage the \ 

276installation of Apio packages. These are not Python packages but \ 

277Apio packages containing various tools and data essential for the \ 

278operation of Apio. 

279 

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

281using and may vary between different operating systems. 

282""" 

283 

284 

285# -- We have only a single group with the title 'Subcommands'. 

286SUBGROUPS = [ 

287 ApioSubgroup( 

288 "Subcommands", 

289 [ 

290 _install_cli, 

291 _list_cli, 

292 ], 

293 ) 

294] 

295 

296 

297@click.command( 

298 name="packages", 

299 cls=ApioGroup, 

300 subgroups=SUBGROUPS, 

301 short_help="Manage the apio packages.", 

302 help=APIO_PACKAGES_HELP, 

303) 

304def cli(): 

305 """Implements the 'apio packages' command group.'""" 

306 

307 # pass