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

70 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-23 03: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""" 

9 

10import sys 

11import click 

12from rich.table import Table 

13from rich import box 

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

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

16from apio.commands import options 

17from apio.managers.package_manager import ( 

18 PackagesScanResults, 

19) 

20from apio.utils.cmd_util import ( 

21 ApioGroup, 

22 ApioSubgroup, 

23 ApioCommand, 

24 ApioOption, 

25) 

26from apio.apio_context import ( 

27 ApioContext, 

28 ProjectPolicy, 

29 RemoteConfigPolicy, 

30 PackagesPolicy, 

31) 

32 

33 

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

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

36 Returns True if the packages are OK. 

37 """ 

38 

39 # -- Scan the packages 

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

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

42 

43 # ===== Required Packages Table ===== 

44 

45 table = Table( 

46 show_header=True, 

47 show_lines=True, 

48 box=box.SQUARE, 

49 border_style=BORDER, 

50 title="Apio Packages Status", 

51 title_justify="left", 

52 padding=(0, 2), 

53 ) 

54 

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

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

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

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

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

60 

61 for package_name, package_status in scan.required_packages.items(): 

62 

63 # -- Collect additional info about the package. 

64 package_manager = apio_ctx.package_manager 

65 installed_version, *_ = package_manager.get_installed_package_info( 

66 package_name 

67 ) 

68 package_info = package_manager.get_required_package_spec(package_name) 

69 

70 # -- Determine row color 

71 row_style = ( 

72 ERROR 

73 if package_status.is_inconsistency 

74 else INFO if not package_status.is_ok else None 

75 ) 

76 

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

78 table.add_row( 

79 package_name, 

80 installed_version, 

81 package_info["description"], 

82 package_status.value, 

83 style=row_style, 

84 ) 

85 

86 # -- Render table. 

87 cout() 

88 ctable(table) 

89 

90 # ===== Orphans Table ===== 

91 

92 # -- Define errors table. 

93 table = Table( 

94 show_header=True, 

95 show_lines=True, 

96 box=box.SQUARE, 

97 border_style=BORDER, 

98 title="Apio Packages Errors", 

99 title_justify="left", 

100 padding=(0, 2), 

101 ) 

102 

103 # -- Add columns. 

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

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

106 

107 # -- Add rows. 

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

109 table.add_row(orphan_name, orphan_type.value) 

110 

111 # -- Render the table, unless empty. 

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

113 cout() 

114 ctable(table) 

115 

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

117 packages_ok = scan.is_all_ok() 

118 

119 cout() 

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

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

122 else: 

123 cout( 

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

125 style=INFO, 

126 ) 

127 

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

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

130 return packages_ok 

131 

132 

133# ------ apio packages install 

134 

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

136APIO_PACKAGES_INSTALL_HELP = """ 

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

138to their latest requirements. 

139 

140Examples:[code] 

141 apio packages install # Install packages 

142 apio pack upd # Same, with shortcuts 

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

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

145 

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

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

148 

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

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

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

152added to the examples package. 

153""" 

154 

155 

156@click.command( 

157 name="install", 

158 cls=ApioCommand, 

159 short_help="Install apio packages.", 

160 help=APIO_PACKAGES_INSTALL_HELP, 

161) 

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

163@options.verbose_option 

164def _install_cli( 

165 *, 

166 # Options 

167 force: bool, 

168 verbose: bool, 

169): 

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

171 

172 apio_ctx = ApioContext( 

173 project_policy=ProjectPolicy.NO_PROJECT, 

174 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

175 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

176 ) 

177 

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

179 # -- of the latest remote config file. 

180 apio_ctx.package_manager.scan_and_fix_inconsistencies() 

181 

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

183 for package in apio_ctx.required_packages: 

184 apio_ctx.package_manager.install_package( 

185 package_name=package, 

186 force_reinstall=force, 

187 verbose=verbose, 

188 ) 

189 

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

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

192 package_ok = print_packages_report2(apio_ctx) 

193 if not package_ok: 

194 sys.exit(1) 

195 

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

197 else: 

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

199 scan = apio_ctx.package_manager.scan_packages() 

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

201 fatal_error( 

202 "Failed to install some packages.", 

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

204 ) 

205 

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

207 # -- the print_packages_report() method. 

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

209 

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

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

212 apio_ctx.package_manager.check_packages_post_install() 

213 

214 

215# ------ apio packages list 

216 

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

218APIO_PACKAGES_LIST_HELP = """ 

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

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

221you are using and may vary between operating systems. 

222 

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

224status code if the packages are not installed properly. 

225 

226Examples:[code] 

227 apio packages list # Just report 

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

229""" 

230 

231check_option = click.option( 

232 "check", # Var name. 

233 "-c", 

234 "--check", 

235 is_flag=True, 

236 help="Error on unhealthy packages.", 

237 cls=ApioOption, 

238) 

239 

240 

241@click.command( 

242 name="list", 

243 cls=ApioCommand, 

244 short_help="List apio packages.", 

245 help=APIO_PACKAGES_LIST_HELP, 

246) 

247@check_option 

248def _list_cli(check: bool): 

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

250 

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

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

253 apio_ctx = ApioContext( 

254 project_policy=ProjectPolicy.NO_PROJECT, 

255 remote_config_policy=RemoteConfigPolicy.GET_FRESH, 

256 packages_policy=PackagesPolicy.IGNORE_PACKAGES, 

257 ) 

258 

259 # -- Print packages report. 

260 packages_ok = print_packages_report2(apio_ctx) 

261 

262 # -- Handle check failure 

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

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

265 

266 # All done OK 

267 

268 

269# ------ apio packages (group) 

270 

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

272APIO_PACKAGES_HELP = """ 

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

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

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

276operation of Apio. 

277 

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

279using and may vary between different operating systems. 

280""" 

281 

282 

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

284SUBGROUPS = [ 

285 ApioSubgroup( 

286 "Subcommands", 

287 [ 

288 _install_cli, 

289 _list_cli, 

290 ], 

291 ) 

292] 

293 

294 

295@click.command( 

296 name="packages", 

297 cls=ApioGroup, 

298 subgroups=SUBGROUPS, 

299 short_help="Manage the apio packages.", 

300 help=APIO_PACKAGES_HELP, 

301) 

302def cli(): 

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

304 

305 # pass