Coverage for apio/commands/apio_api.py: 90%

331 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 api' command""" 

9 

10# pylint: disable=too-many-lines 

11 

12import sys 

13import os 

14from typing import Self, cast, Any 

15from dataclasses import dataclass 

16import json 

17from pathlib import Path 

18import click 

19from apio.commands import options 

20from apio.managers.examples import Examples, ExampleInfo 

21from apio.common.apio_console import cout, fatal_error 

22from apio.common import proto_util 

23from apio.common.debug_util import is_under_vscode_debugger 

24from apio.common.common_util import get_project_source_files 

25from apio.utils import ( 

26 cmd_util, 

27 usb_util, 

28 serial_util, 

29 util, 

30 apio_platforms, 

31 env_options, 

32) 

33from apio.utils.usb_util import UsbDevice 

34from apio.utils.serial_util import SerialDevice 

35from apio.common.apio_styles import ( 

36 INFO, 

37 ERROR, 

38 SUCCESS, 

39 WARNING, 

40 EMPH1, 

41 EMPH2, 

42 EMPH3, 

43 TITLE, 

44) 

45from apio.apio_context import ( 

46 ApioContext, 

47 PackagesPolicy, 

48 ProjectPolicy, 

49 RemoteConfigPolicy, 

50) 

51from apio.utils.cmd_util import ( 

52 ApioGroup, 

53 ApioSubgroup, 

54 ApioCommand, 

55 ApioCmdContext, 

56) 

57from apio.common import build_report 

58 

59timestamp_option = click.option( 

60 "timestamp", # Var name. 

61 "-t", 

62 "--timestamp", 

63 type=str, 

64 metavar="text", 

65 help="Set a user provided timestamp.", 

66 cls=cmd_util.ApioOption, 

67) 

68 

69output_option = click.option( 

70 "output", # Var name. 

71 "-o", 

72 "--output", 

73 type=str, 

74 metavar="file-name", 

75 help="Set output file.", 

76 cls=cmd_util.ApioOption, 

77) 

78 

79 

80def write_as_json_doc(top_dict: dict, output_flag: str, force_flag: bool): 

81 """A common function to write a dict as a JSON doc.""" 

82 # -- Format the top dict as json text. 

83 text = json.dumps(top_dict, indent=2) 

84 

85 if output_flag: 

86 # -- Output the json text to a user specified file. 

87 output_path = Path(output_flag) 

88 

89 if output_path.is_dir(): 89 ↛ 90line 89 didn't jump to line 90 because the condition on line 89 was never true

90 fatal_error(f"The output path '{output_path}' is a directory.") 

91 

92 if output_path.exists() and not force_flag: 92 ↛ 93line 92 didn't jump to line 93 because the condition on line 92 was never true

93 fatal_error( 

94 f"The file already exists '{output_path}'.", 

95 info="Use the --force option to allow overwriting.", 

96 ) 

97 

98 # -- if there file path contains a parent dir, make 

99 # -- sure it exists. If output_flag is just a file name such 

100 # -- as 'foo.json', we don nothing. 

101 dirname = os.path.dirname(output_flag) 

102 if dirname: 102 ↛ 106line 102 didn't jump to line 106 because the condition on line 102 was always true

103 os.makedirs(dirname, exist_ok=True) 

104 

105 # -- Write to file. 

106 with open(output_flag, "w", encoding="utf-8") as f: 

107 f.write(text) 

108 else: 

109 # -- Output the json text to stdout. 

110 print(text, file=sys.stdout) 

111 

112 

113# ------ apio api get-system 

114 

115 

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

117APIO_API_GET_SYSTEM_HELP = """ 

118The command 'apio api get-system' exports information about apio and \ 

119the underlying system as a JSON foc. It is similar to the command \ 

120'apio info system' which is intended for human consumption. 

121 

122The optional flag '--timestamp' allows the caller to embed in the JSON \ 

123document a known timestamp that allows to verify that the JSON document \ 

124was indeed was generated by the same invocation. 

125 

126Examples:[code] 

127 apio api get-system # Write to stdout 

128 apio api get-system -o apio.json # Write to a file[/code] 

129""" 

130 

131 

132@click.command( 

133 name="get-system", 

134 cls=ApioCommand, 

135 short_help="Retrieve apio and system information.", 

136 help=APIO_API_GET_SYSTEM_HELP, 

137) 

138# @click.pass_context 

139@timestamp_option 

140@output_option 

141@options.force_option_gen(short_help="Overwrite output file.") 

142def _get_system_cli( 

143 *, 

144 # Options 

145 timestamp: str, 

146 output: str, 

147 force: bool, 

148): 

149 """Implements the 'apio apio get-system' command.""" 

150 

151 apio_ctx = ApioContext( 

152 project_policy=ProjectPolicy.NO_PROJECT, 

153 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

154 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

155 ) 

156 

157 platform = apio_ctx.platform 

158 

159 # -- The top dict that we will emit as json. 

160 top_dict: dict[str, Any] = {} 

161 

162 # -- Append user timestamp if specified. 

163 if timestamp: 163 ↛ 166line 163 didn't jump to line 166 because the condition on line 163 was always true

164 top_dict["timestamp"] = timestamp 

165 

166 section_dict: dict[str, Any] = {} 

167 

168 # -- Add fields. 

169 section_dict["apio-cli-version"] = util.get_apio_version_str() 

170 section_dict["release-info"] = util.get_apio_release_info() 

171 section_dict["yosys-release-tag"] = ( 

172 apio_ctx.package_manager.get_yosys_release_tag() 

173 ) 

174 section_dict["python-version"] = util.get_python_version() 

175 section_dict["python-executable"] = sys.executable 

176 section_dict["platform-info"] = apio_platforms.get_system_info() 

177 section_dict["platform-id"] = platform.id 

178 section_dict["is-darwin"] = platform.is_darwin 

179 section_dict["is-linux"] = platform.is_linux 

180 section_dict["is-windows"] = platform.is_windows 

181 section_dict["scons-shell-id"] = apio_ctx.scons_shell_id 

182 section_dict["vscode-debugger"] = str(is_under_vscode_debugger()).lower() 

183 section_dict["pyinstaller"] = str(util.is_pyinstaller_app()).lower() 

184 section_dict["apio-python_package"] = str( 

185 util.get_path_in_apio_package("") 

186 ) 

187 section_dict["apio-home-dir"] = str(apio_ctx.apio_home_dir) 

188 section_dict["apio-packages-dir"] = str(apio_ctx.apio_packages_dir) 

189 section_dict["remote-config-url"] = ( 

190 apio_ctx.remote_config.remote_config_url 

191 ) 

192 section_dict["verible-formatter"] = str( 

193 apio_ctx.apio_packages_dir / "verible/bin/verible-verilog-format" 

194 ) 

195 section_dict["verible-language-server"] = str( 

196 apio_ctx.apio_packages_dir / "verible/bin/verible-verilog-ls" 

197 ) 

198 

199 # -- Include all supported apio env vars, defined and undefined. 

200 apio_vars_dict = { 

201 var: env_options.get(var, None) for var in env_options.get_all() 

202 } 

203 section_dict["apio-env-vars"] = apio_vars_dict 

204 

205 # -- Add section 

206 top_dict["system"] = section_dict 

207 

208 # -- Write out 

209 write_as_json_doc(top_dict, output, force) 

210 

211 

212# ------ apio api get-build-report 

213 

214 

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

216APIO_API_GET_BUILD_REPORT_HELP = """ 

217The command 'apio api get-build-report' provides utilization and max \ 

218clock information from a built project. The information is extracted \ 

219from the file 'hardware.pnr' that is generated by Apio when building \ 

220the project. 

221 

222The optional flag '--timestamp' allows the caller to embed in the JSON \ 

223document a known timestamp that allows to verify that the JSON document \ 

224was indeed was generated by the same invocation. 

225 

226Examples:[code] 

227 apio api get-build-report # Report for default env 

228 apio api get-build-report -e env1 # Report for specified env 

229 apio api get-build-report -p foo/bar # Project in another dir 

230 apio api get-build-report -o apio.json # Write to a file[/code] 

231""" 

232 

233 

234@click.command( 

235 name="get-build-report", 

236 cls=ApioCommand, 

237 short_help="Get project build information.", 

238 help=APIO_API_GET_BUILD_REPORT_HELP, 

239) 

240# @click.pass_context 

241@options.env_option_gen() 

242@options.project_dir_option 

243@timestamp_option 

244@output_option 

245@options.force_option_gen(short_help="Overwrite output file.") 

246def _get_build_report_cli( 

247 *, 

248 # Options 

249 env: str, 

250 project_dir: Path | None, 

251 timestamp: str, 

252 output: str, 

253 force: bool, 

254): 

255 """Implements the 'apio apio get-build-report' command.""" 

256 

257 apio_ctx = ApioContext( 

258 project_policy=ProjectPolicy.PROJECT_REQUIRED, 

259 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

260 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

261 project_dir_arg=project_dir, 

262 env_arg=env, 

263 report_env=False, 

264 ) 

265 

266 # -- Change to the project's folder. 

267 os.chdir(apio_ctx.project_dir) 

268 

269 # -- The build process generates this report file. 

270 pnr_json_file = apio_ctx.env_build_path / "hardware.pnr" 

271 

272 # -- Read the report 

273 report = build_report.read_build_report(pnr_json_file) 

274 

275 # -- The top dict that we will emit as json. 

276 top_dict: dict[str, Any] = {} 

277 

278 # -- Append user timestamp if specified. 

279 if timestamp: 279 ↛ 282line 279 didn't jump to line 282 because the condition on line 279 was always true

280 top_dict["timestamp"] = timestamp 

281 

282 section_dict: dict[str, Any] = {} 

283 section_dict["env"] = apio_ctx.project.env_name 

284 

285 resources_dict: dict[str, Any] = {} 

286 for res in report.resources: 

287 resources_dict[res.name] = { 

288 "used": res.used, 

289 "available": res.available, 

290 "percentage": res.percentage, 

291 } 

292 

293 section_dict["resources"] = resources_dict 

294 

295 clocks_dict: dict[str, Any] = {} 

296 for clk in report.clocks: 

297 clocks_dict[clk.name] = {"fmax_mhz": clk.fmax_mhz} 

298 

299 section_dict["clocks"] = clocks_dict 

300 

301 # -- Add section 

302 top_dict["build-report"] = section_dict 

303 

304 # -- Write out 

305 write_as_json_doc(top_dict, output, force) 

306 

307 

308# ------ apio api get-project 

309 

310 

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

312APIO_API_GET_PROJECT_HELP = """ 

313The command 'apio api get-project' exports information about an Apio 

314project as a JSON foc. 

315 

316The optional flag '--timestamp' allows the caller to embed in the JSON \ 

317document a known timestamp that allows to verify that the JSON document \ 

318was indeed was generated by the same invocation. 

319 

320Examples:[code] 

321 apio api get-project # Report default env 

322 apio api get-project -e env1 # Report specified env 

323 apio api get-project -p foo/bar # Project in another dir 

324 apio api get-project -o apio.json # Write to a file[/code] 

325""" 

326 

327 

328@click.command( 

329 name="get-project", 

330 cls=ApioCommand, 

331 short_help="Get project information.", 

332 help=APIO_API_GET_PROJECT_HELP, 

333) 

334# @click.pass_context 

335@options.env_option_gen() 

336@options.project_dir_option 

337@timestamp_option 

338@output_option 

339@options.force_option_gen(short_help="Overwrite output file.") 

340def _get_project_cli( 

341 *, 

342 # Options 

343 env: str, 

344 project_dir: Path | None, 

345 timestamp: str, 

346 output: str, 

347 force: bool, 

348): 

349 """Implements the 'apio apio get-project' command.""" 

350 

351 apio_ctx = ApioContext( 

352 project_policy=ProjectPolicy.PROJECT_REQUIRED, 

353 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

354 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

355 project_dir_arg=project_dir, 

356 env_arg=env, 

357 report_env=False, 

358 ) 

359 assert apio_ctx.definitions is not None 

360 

361 # -- Change to the project's folder for function such as 

362 # -- get_project_source_files() which expects to run at the project root. 

363 os.chdir(apio_ctx.project_dir) 

364 

365 # -- The top dict that we will emit as json. 

366 top_dict: dict[str, Any] = {} 

367 

368 # -- Append user timestamp if specified. 

369 if timestamp: 369 ↛ 372line 369 didn't jump to line 372 because the condition on line 369 was always true

370 top_dict["timestamp"] = timestamp 

371 

372 section_dict: dict[str, Any] = {} 

373 

374 active_env_dict: dict[str, Any] = {} 

375 active_env_dict["name"] = apio_ctx.project.env_name 

376 active_env_dict["options"] = apio_ctx.project.env_options 

377 section_dict["active-env"] = active_env_dict 

378 

379 section_dict["envs"] = apio_ctx.project.env_names 

380 

381 synth_srcs, test_srcs = get_project_source_files() 

382 section_dict["synth-files"] = synth_srcs 

383 section_dict["test-benches"] = test_srcs 

384 

385 pr = apio_ctx.project_resources 

386 

387 board_dict = { 

388 "id": pr.board_id, 

389 "is-custom": apio_ctx.definitions.is_custom_board(pr.board_id), 

390 } 

391 board_dict["definition"] = proto_util.proto_to_json_dict( 

392 pr.board_definition 

393 ) 

394 section_dict["board"] = board_dict 

395 

396 fpga_dict = { 

397 "id": pr.fpga_id, 

398 "is-custom": apio_ctx.definitions.is_custom_fpga(pr.fpga_id), 

399 } 

400 fpga_dict["definition"] = proto_util.proto_to_json_dict(pr.fpga_definition) 

401 section_dict["fpga"] = fpga_dict 

402 

403 programmer_dict = { 

404 "id": pr.programmer_id, 

405 "is-custom": apio_ctx.definitions.is_custom_programmer( 

406 pr.programmer_id 

407 ), 

408 } 

409 programmer_dict["definition"] = proto_util.proto_to_json_dict( 

410 pr.programmer_definition 

411 ) 

412 section_dict["programmer"] = programmer_dict 

413 

414 # -- Add section 

415 top_dict["project"] = section_dict 

416 

417 # -- Write out 

418 write_as_json_doc(top_dict, output, force) 

419 

420 

421# ------ apio api get-boards 

422 

423 

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

425APIO_API_GET_BOARDS_HELP = """ 

426The command 'apio api get-boards' exports apio boards information as a \ 

427JSON document. 

428 

429The optional flag '--timestamp' allows the caller to embed in the JSON \ 

430document a known timestamp that allows to verify that the JSON document \ 

431was indeed was generated by the same invocation. 

432 

433Examples:[code] 

434 apio api get-boards # Write to stdout 

435 apio api get-boards -o apio.json # Write to a file[/code] 

436""" 

437 

438 

439@click.command( 

440 name="get-boards", 

441 cls=ApioCommand, 

442 short_help="Retrieve boards information.", 

443 help=APIO_API_GET_BOARDS_HELP, 

444) 

445@timestamp_option 

446@output_option 

447@options.force_option_gen(short_help="Overwrite output file.") 

448def _get_boards_cli( 

449 *, 

450 # Options 

451 timestamp: str, 

452 output: str, 

453 force: bool, 

454): 

455 """Implements the 'apio apio get-boards' command.""" 

456 

457 # -- For now, the information is not in a project context. That may 

458 # -- change in the future. 

459 apio_ctx = ApioContext( 

460 project_policy=ProjectPolicy.NO_PROJECT, 

461 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

462 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

463 ) 

464 assert apio_ctx.definitions is not None 

465 

466 # -- The top dict that we will emit as json. 

467 top_dict: dict[str, Any] = {} 

468 

469 # -- Append user timestamp if specified. 

470 if timestamp: 470 ↛ 474line 470 didn't jump to line 474 because the condition on line 470 was always true

471 top_dict["timestamp"] = timestamp 

472 

473 # -- Generate the boards section. 

474 section: dict[str, Any] = {} 

475 for board_id, board_definition in apio_ctx.definitions.boards.items(): 

476 # -- The board output dict. 

477 board_dict: dict[str, Any] = {} 

478 

479 # -- We assume that these proto fields are requires and therefore 

480 # -- must exist. 

481 proto_util.check_is_required( 

482 board_definition, 

483 "description", 

484 "fpga_id", 

485 "programmer", 

486 "programmer.id", 

487 ) 

488 

489 # -- Add board description 

490 board_dict["description"] = board_definition.description 

491 

492 # -- Add board's fpga information. 

493 fpga_id = board_definition.fpga_id 

494 fpga_definition = apio_ctx.definitions.fpgas[fpga_id] 

495 fpga_dict = {"id": fpga_id} 

496 fpga_dict.update(proto_util.proto_to_json_dict(fpga_definition)) 

497 board_dict["fpga"] = fpga_dict 

498 

499 # -- Add board's programmer information. 

500 programmer_dict = {} 

501 programmer_id = board_definition.programmer.id 

502 programmer_dict["id"] = programmer_id 

503 board_dict["programmer"] = programmer_dict 

504 

505 # -- Add the board to the boards dict. 

506 section[board_id] = board_dict 

507 

508 top_dict["boards"] = section 

509 

510 # -- Write out 

511 write_as_json_doc(top_dict, output, force) 

512 

513 

514# ------ apio api get-fpgas 

515 

516 

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

518APIO_API_GET_FPGAS_HELP = """ 

519The command 'apio api get-fpgas' exports apio FPGAss information as a \ 

520JSON document. 

521 

522The optional flag '--timestamp' allows the caller to embed in the JSON \ 

523document a known timestamp that allows to verify that the JSON document \ 

524was indeed was generated by the same invocation. 

525 

526Examples:[code] 

527 apio api get-fpgas # Write to stdout 

528 apio api get-fpgas -o apio.json # Write to a file[/code] 

529""" 

530 

531 

532@click.command( 

533 name="get-fpgas", 

534 cls=ApioCommand, 

535 short_help="Retrieve FPGAs information.", 

536 help=APIO_API_GET_FPGAS_HELP, 

537) 

538@timestamp_option 

539@output_option 

540@options.force_option_gen(short_help="Overwrite output file.") 

541def _get_fpgas_cli( 

542 *, 

543 # Options 

544 timestamp: str, 

545 output: str, 

546 force: bool, 

547): 

548 """Implements the 'apio apio get-fpgas' command.""" 

549 

550 # -- For now, the information is not in a project context. That may 

551 # -- change in the future. 

552 apio_ctx = ApioContext( 

553 project_policy=ProjectPolicy.NO_PROJECT, 

554 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

555 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

556 ) 

557 assert apio_ctx.definitions is not None 

558 

559 # -- The top dict that we will emit as json. 

560 top_dict: dict[str, Any] = {} 

561 

562 # -- Append user timestamp if specified. 

563 if timestamp: 563 ↛ 567line 563 didn't jump to line 567 because the condition on line 563 was always true

564 top_dict["timestamp"] = timestamp 

565 

566 # -- Generate the fpgas section 

567 section: dict[str, Any] = {} 

568 for fpga_id, fpga_definition in apio_ctx.definitions.fpgas.items(): 

569 section[fpga_id] = proto_util.proto_to_json_dict(fpga_definition) 

570 

571 top_dict["fpgas"] = section 

572 

573 # -- Write out 

574 write_as_json_doc(top_dict, output, force) 

575 

576 

577# ------ apio api get-programmers 

578 

579 

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

581APIO_API_GET_PROGRAMMERS_HELP = """ 

582The command 'apio api get-programmers' exports apio programmers information \ 

583as a JSON document. 

584 

585The optional flag '--timestamp' allows the caller to embed in the JSON \ 

586document a known timestamp that allows to verify that the JSON document \ 

587was indeed was generated by the same invocation. 

588 

589Examples:[code] 

590 apio api get-programmers # Write to stdout 

591 apio api get-programmers -o apio.json # Write to a file[/code] 

592""" 

593 

594 

595@click.command( 

596 name="get-programmers", 

597 cls=ApioCommand, 

598 short_help="Retrieve programmers information.", 

599 help=APIO_API_GET_PROGRAMMERS_HELP, 

600) 

601@timestamp_option 

602@output_option 

603@options.force_option_gen(short_help="Overwrite output file.") 

604def _get_programmers_cli( 

605 *, 

606 # Options 

607 timestamp: str, 

608 output: str, 

609 force: bool, 

610): 

611 """Implements the 'apio apio get-programmers' command.""" 

612 

613 # -- For now, the information is not in a project context. That may 

614 # -- change in the future. 

615 apio_ctx = ApioContext( 

616 project_policy=ProjectPolicy.NO_PROJECT, 

617 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

618 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

619 ) 

620 assert apio_ctx.definitions is not None 

621 

622 # -- The top dict that we will emit as json. 

623 top_dict: dict[str, Any] = {} 

624 

625 # -- Append user timestamp if specified. 

626 if timestamp: 626 ↛ 630line 626 didn't jump to line 630 because the condition on line 626 was always true

627 top_dict["timestamp"] = timestamp 

628 

629 # -- Generate the 'programmers' section. 

630 section: dict[str, Any] = {} 

631 for ( 

632 programmer_id, 

633 programmer_definition, 

634 ) in apio_ctx.definitions.programmers.items(): 

635 section[programmer_id] = proto_util.proto_to_json_dict( 

636 programmer_definition 

637 ) 

638 

639 top_dict["programmers"] = section 

640 

641 # -- Write out 

642 write_as_json_doc(top_dict, output, force) 

643 

644 

645# ------ apio api get-examples 

646 

647 

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

649APIO_API_GET_EXAMPLES_HELP = """ 

650The command 'apio api get-examples' exports apio examples information as a \ 

651JSON document. 

652 

653The optional flag '--timestamp' allows the caller to embed in the JSON \ 

654document a known timestamp that allows to verify that the JSON document \ 

655was indeed was generated by the same invocation. 

656 

657Examples:[code] 

658 apio api get-examples # Write to stdout 

659 apio api get-examples -o apio.json # Write to a file[/code] 

660""" 

661 

662 

663@click.command( 

664 name="get-examples", 

665 cls=ApioCommand, 

666 short_help="Retrieve examples information.", 

667 help=APIO_API_GET_EXAMPLES_HELP, 

668) 

669@timestamp_option 

670@output_option 

671@options.force_option_gen(short_help="Overwrite output file.") 

672def _get_examples_cli( 

673 *, 

674 # Options 

675 timestamp: str, 

676 output: str, 

677 force: bool, 

678): 

679 """Implements the 'apio apio get-examples' command.""" 

680 

681 # -- For now, the information is not in a project context. That may 

682 # -- change in the future. 

683 apio_ctx = ApioContext( 

684 project_policy=ProjectPolicy.NO_PROJECT, 

685 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

686 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

687 ) 

688 

689 # -- Get examples infos. 

690 examples: list[ExampleInfo] = Examples(apio_ctx).get_examples_infos() 

691 

692 # -- Group examples by boards 

693 boards_examples: dict[str, list[ExampleInfo]] = {} 

694 for example in examples: 

695 board_examples = boards_examples.get(example.board_id, []) 

696 board_examples.append(example) 

697 boards_examples[example.board_id] = board_examples 

698 

699 # -- The top dict that we will emit as json. 

700 top_dict: dict[str, Any] = {} 

701 

702 # -- Append user timestamp if specified. 

703 if timestamp: 703 ↛ 707line 703 didn't jump to line 707 because the condition on line 703 was always true

704 top_dict["timestamp"] = timestamp 

705 

706 # -- Generate the 'examples' section. 

707 section: dict[str, Any] = {} 

708 for board, board_examples in boards_examples.items(): 

709 board_dict = {} 

710 # -- Generate board examples 

711 for example_info in board_examples: 

712 example_dict = {} 

713 example_dict["description"] = example_info.description 

714 board_dict[example_info.example_name] = example_dict 

715 

716 section[board] = board_dict 

717 

718 top_dict["examples"] = section 

719 

720 # -- Write out 

721 write_as_json_doc(top_dict, output, force) 

722 

723 

724# ------ apio api get-commands 

725 

726 

727@dataclass(frozen=True) 

728class CmdInfo: 

729 """Represents the information of a single apio command.""" 

730 

731 name: str 

732 path: list[str] 

733 cli: click.Command 

734 children: list[Self] 

735 

736 

737def scan_children(cmd_cli) -> dict: 

738 """Return a dict describing this command subtree.""" 

739 result: dict[str, Any] = {} 

740 

741 # -- Sanity check 

742 assert isinstance(result, dict), type(result) 

743 

744 # -- If this is a simple command, it has no sub commands. 

745 if isinstance(cmd_cli, ApioCommand): 

746 return result 

747 

748 # -- Here we have a group and it should have at least one sub command. 

749 assert isinstance(cmd_cli, ApioGroup), type(cmd_cli) 

750 subgroups: list[ApioSubgroup] = cmd_cli.subgroups 

751 

752 # -- Create the dict for the command subgroups. 

753 subcommands_dict: dict[str, Any] = {} 

754 result["commands"] = subcommands_dict 

755 

756 # -- Iterate the subgroups and populate them. We flaten the subcommands 

757 # -- group into a single list of commands. 

758 for subgroup in subgroups: 

759 assert isinstance(subgroup, ApioSubgroup), type(subgroup) 

760 assert isinstance(subgroup.title, str), type(subgroup.title) 

761 for subcommand in subgroup.commands: 

762 subcommand_dict = scan_children(subcommand) 

763 assert subcommand.name is not None 

764 subcommands_dict[subcommand.name] = subcommand_dict 

765 

766 # -- All done ok. 

767 return result 

768 

769 

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

771APIO_API_GET_COMMANDS_HELP = """ 

772The command 'apio api get-commands' exports apio command structure \ 

773of Apio as a JSON doc. This is used by various tools such as 

774documentation generators and tests. 

775 

776The optional flag '--timestamp' allows the caller to embed in the JSON \ 

777document a known timestamp that allows to verify that the JSON document \ 

778was indeed was generated by the same invocation. 

779 

780Examples:[code] 

781 apio api get-commands # Write to stdout 

782 apio api get-commands -o apio.json # Write to a file[/code] 

783""" 

784 

785 

786@click.command( 

787 name="get-commands", 

788 cls=ApioCommand, 

789 short_help="Retrieve apio commands information.", 

790 help=APIO_API_GET_COMMANDS_HELP, 

791) 

792@click.pass_context 

793@timestamp_option 

794@output_option 

795@options.force_option_gen(short_help="Overwrite output file.") 

796def _get_commands_cli( 

797 # Click context 

798 cmd_ctx: click.Context, 

799 *, 

800 # Options 

801 timestamp: str, 

802 output: str, 

803 force: bool, 

804): 

805 """Implements the 'apio apio get-commands' command.""" 

806 

807 # -- Find the top cli which is the "apio" command. Would access it 

808 # -- directly but it would create a circular python import. 

809 ctx = cast(ApioCmdContext, cmd_ctx) 

810 while ctx.parent: 

811 assert isinstance(ctx.parent, ApioCmdContext), type(ctx.parent) 

812 ctx = ctx.parent 

813 top_cli = ctx.command 

814 assert top_cli.name == "apio", top_cli 

815 

816 # -- This initializes the console, print active env vars, etc. 

817 ApioContext( 

818 project_policy=ProjectPolicy.NO_PROJECT, 

819 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

820 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

821 ) 

822 

823 # -- The top dict that we will emit as json. 

824 top_dict: dict[str, Any] = {} 

825 

826 # -- Append user timestamp if specified. 

827 if timestamp: 827 ↛ 830line 827 didn't jump to line 830 because the condition on line 827 was always true

828 top_dict["timestamp"] = timestamp 

829 

830 section_dict: dict[str, Any] = {} 

831 section_dict["apio"] = scan_children(top_cli) 

832 top_dict["commands"] = section_dict 

833 

834 # -- Write out 

835 write_as_json_doc(top_dict, output, force) 

836 

837 

838# ------ apio api get-packages 

839 

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

841APIO_API_GET_PACKAGES_HELP = """ 

842The command 'apio api get-packages' exports information about \ 

843the apio packages on the current platform as a JSON foc. Note that \ 

844the set of apio packages used depends on the platform used as some \ 

845packages may be platform specific. 

846 

847The optional flag '--timestamp' allows the caller to embed in the JSON \ 

848document a known timestamp that allows to verify that the JSON document \ 

849was indeed was generated by the same invocation. 

850 

851Examples:[code] 

852 apio api get-packages # Write to stdout 

853 apio api get-packages -o apio.json # Write to a file[/code] 

854""" 

855 

856 

857@click.command( 

858 name="get-packages", 

859 cls=ApioCommand, 

860 short_help="Retrieve apio packages information.", 

861 help=APIO_API_GET_PACKAGES_HELP, 

862) 

863# @click.pass_context 

864@timestamp_option 

865@output_option 

866@options.force_option_gen(short_help="Overwrite output file.") 

867def _get_packages_cli( 

868 *, 

869 # Options 

870 timestamp: str, 

871 output: str, 

872 force: bool, 

873): 

874 """Implements the 'apio apio get-packages' command.""" 

875 

876 apio_ctx = ApioContext( 

877 project_policy=ProjectPolicy.NO_PROJECT, 

878 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

879 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

880 ) 

881 

882 package_manager = apio_ctx.package_manager 

883 

884 # -- The top dict that we will emit as json. 

885 top_dict: dict[str, Any] = {} 

886 

887 # -- Append user timestamp if specified. 

888 if timestamp: 888 ↛ 892line 888 didn't jump to line 892 because the condition on line 888 was always true

889 top_dict["timestamp"] = timestamp 

890 

891 # -- Packages section 

892 section_dict: dict[str, Any] = {} 

893 top_dict["packages"] = section_dict 

894 

895 for package_name in package_manager.required_packages: 

896 metadata = package_manager.installed_packages[package_name] 

897 config = apio_ctx.all_packages[package_name] 

898 build_info = package_manager.read_package_build_info(package_name) 

899 section_dict[package_name] = { 

900 "description": config["description"], 

901 "installation": metadata, 

902 "build-info": build_info, 

903 "env": config["env"], 

904 } 

905 

906 # -- Write out 

907 write_as_json_doc(top_dict, output, force) 

908 

909 

910# ------ apio api scan-devices 

911 

912 

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

914APIO_API_SCAN_DEVICES_HELP = """ 

915The command 'apio api scan-devices' scans and report the available usb and \ 

916serial devices. 

917 

918The optional flag '--timestamp' allows the caller to embed in the JSON \ 

919document a known timestamp that allows to verify that the JSON document \ 

920was indeed was generated by the same invocation. 

921 

922Examples:[code] 

923 apio api scan-devices # Write to stdout 

924 apio api scan-devices -o apio.json # Write to a file[/code] 

925""" 

926 

927 

928@click.command( 

929 name="scan-devices", 

930 cls=ApioCommand, 

931 short_help="Scan and report available devices.", 

932 help=APIO_API_SCAN_DEVICES_HELP, 

933) 

934@timestamp_option 

935@output_option 

936@options.force_option_gen(short_help="Overwrite output file.") 

937def _scan_devices_cli( 

938 *, 

939 # Options 

940 timestamp: str, 

941 output: str, 

942 force: bool, 

943): 

944 """Implements the 'apio apio scan-devices' command.""" 

945 

946 # -- For now, the information is not in a project context. That may 

947 # -- change in the future. We need the packages for the libusb operation. 

948 apio_ctx = ApioContext( 

949 project_policy=ProjectPolicy.NO_PROJECT, 

950 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

951 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

952 ) 

953 

954 # -- The top dict that we will emit as json. 

955 top_dict: dict[str, Any] = {} 

956 

957 # -- Append user timestamp if specified. 

958 if timestamp: 958 ↛ 961line 958 didn't jump to line 961 because the condition on line 958 was always true

959 top_dict["timestamp"] = timestamp 

960 

961 usb_devices: list[UsbDevice] = usb_util.scan_usb_devices(apio_ctx) 

962 

963 # -- Scan and report usb devices. 

964 section = [] 

965 for usb_device in usb_devices: 965 ↛ 966line 965 didn't jump to line 966 because the loop on line 965 never started

966 dev = {} 

967 dev["vid"] = usb_device.vid 

968 dev["pid"] = usb_device.pid 

969 dev["bus"] = str(usb_device.bus) 

970 dev["device"] = str(usb_device.device) 

971 dev["manufacturer"] = usb_device.manufacturer 

972 dev["product"] = usb_device.product 

973 dev["serial-number"] = usb_device.serial_number 

974 dev["device_type"] = usb_device.device_type 

975 

976 section.append(dev) 

977 

978 top_dict["usb-devices"] = section 

979 

980 # -- Scan and report serial devices. 

981 serial_devices: list[SerialDevice] = serial_util.scan_serial_devices( 

982 apio_ctx 

983 ) 

984 

985 section = [] 

986 for serial_device in serial_devices: 986 ↛ 987line 986 didn't jump to line 987 because the loop on line 986 never started

987 dev = {} 

988 dev["port"] = serial_device.port 

989 dev["port-name"] = serial_device.port_name 

990 dev["vendor-id"] = serial_device.vid 

991 dev["product-id"] = serial_device.pid 

992 dev["manufacturer"] = serial_device.manufacturer 

993 dev["product"] = serial_device.product 

994 dev["serial-number"] = serial_device.serial_number 

995 dev["device-type"] = serial_device.device_type 

996 

997 section.append(dev) 

998 

999 top_dict["serial-devices"] = section 

1000 

1001 # -- Write out 

1002 write_as_json_doc(top_dict, output, force) 

1003 

1004 

1005# ------ apio api echo 

1006 

1007 

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

1009APIO_API_ECHO_HELP = """ 

1010The command 'apio api echo' allows external programs such as the Apio VS Code \ 

1011extension to print a short message in a format that is consistent with \ 

1012that Apio theme that is currently selected in the user preferences. 

1013 

1014The required option '--style' should have one of these values: OK, \ 

1015INFO, WARNING, ERROR, TITLE, EMPH1, EMPH2, or EMPH3. The style colors can \ 

1016be viewed with the command 'apio info themes'. 

1017 

1018Examples:[code] 

1019 apio api echo -t "Hello world", -s "INFO" 

1020 apio api echo -t "Task completed successfully", -s "OK" 

1021 apio api echo -t "Task failed", -s "ERROR"[/code] 

1022""" 

1023 

1024# -- Supported style names 

1025STYLES = { 

1026 "OK": SUCCESS, 

1027 "INFO": INFO, 

1028 "WARNING": WARNING, 

1029 "ERROR": ERROR, 

1030 "TITLE": TITLE, 

1031 "EMPH1": EMPH1, 

1032 "EMPH2": EMPH2, 

1033 "EMPH3": EMPH3, 

1034} 

1035 

1036text_option = click.option( 

1037 "text", # Var name. 

1038 "-t", 

1039 "--text", 

1040 type=str, 

1041 metavar="MESSAGE", 

1042 required=True, 

1043 help="Set message to echo.", 

1044 cls=cmd_util.ApioOption, 

1045) 

1046 

1047 

1048style_option = click.option( 

1049 "style", # Var name. 

1050 "-s", 

1051 "--style", 

1052 type=click.Choice(STYLES.keys()), 

1053 metavar="STYLE", 

1054 required=True, 

1055 help="Set style to use.", 

1056 cls=cmd_util.ApioOption, 

1057) 

1058 

1059 

1060@click.command( 

1061 name="echo", 

1062 cls=ApioCommand, 

1063 short_help="Print a message in given format.", 

1064 help=APIO_API_ECHO_HELP, 

1065) 

1066@text_option 

1067@style_option 

1068def _echo_cli( 

1069 *, 

1070 # Options 

1071 text: str, 

1072 style: str, 

1073): 

1074 """Implements the 'apio apio echo command.""" 

1075 

1076 # -- Instanatiate Apio context, project and packages are not needed. 

1077 _ = ApioContext( 

1078 project_policy=ProjectPolicy.NO_PROJECT, 

1079 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

1080 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

1081 ) 

1082 

1083 cout(text, style=STYLES[style]) 

1084 

1085 

1086# ------ apio apio 

1087 

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

1089APIO_API_HELP = """ 

1090The command group 'apio api' contains subcommands that that are intended \ 

1091to be used by tools and programs such as icestudio, rather than being used \ 

1092directly by users. 

1093""" 

1094 

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

1096SUBGROUPS = [ 

1097 ApioSubgroup( 

1098 "Subcommands", 

1099 [ 

1100 _get_system_cli, 

1101 _get_project_cli, 

1102 _get_build_report_cli, 

1103 _get_boards_cli, 

1104 _get_fpgas_cli, 

1105 _get_programmers_cli, 

1106 _get_examples_cli, 

1107 _get_commands_cli, 

1108 _get_packages_cli, 

1109 _scan_devices_cli, 

1110 _echo_cli, 

1111 ], 

1112 ) 

1113] 

1114 

1115 

1116@click.command( 

1117 name="api", 

1118 cls=ApioGroup, 

1119 subgroups=SUBGROUPS, 

1120 short_help="Apio programmatic interface.", 

1121 help=APIO_API_HELP, 

1122) 

1123def cli(): 

1124 """Implements the 'apio apio' command group.""" 

1125 

1126 # pass