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

305 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-09 01:55 +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 Dict, List, Self, Optional, cast 

15from dataclasses import dataclass 

16import json 

17from pathlib import Path 

18import click 

19from apio.commands import options 

20 

21# from apio.managers import packages 

22from apio.managers.examples import Examples, ExampleInfo 

23from apio.common.apio_console import cout, cerror 

24from apio.common.common_util import get_project_source_files 

25from apio.utils import cmd_util, usb_util, serial_util, util, apio_platforms 

26from apio.utils.usb_util import UsbDevice 

27from apio.utils.serial_util import SerialDevice 

28from apio.common.apio_styles import ( 

29 INFO, 

30 ERROR, 

31 SUCCESS, 

32 WARNING, 

33 EMPH1, 

34 EMPH2, 

35 EMPH3, 

36 TITLE, 

37) 

38from apio.apio_context import ( 

39 ApioContext, 

40 PackagesPolicy, 

41 ProjectPolicy, 

42 RemoteConfigPolicy, 

43) 

44from apio.utils.cmd_util import ( 

45 ApioGroup, 

46 ApioSubgroup, 

47 ApioCommand, 

48 ApioCmdContext, 

49) 

50from apio.common import build_report 

51 

52timestamp_option = click.option( 

53 "timestamp", # Var name. 

54 "-t", 

55 "--timestamp", 

56 type=str, 

57 metavar="text", 

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

59 cls=cmd_util.ApioOption, 

60) 

61 

62output_option = click.option( 

63 "output", # Var name. 

64 "-o", 

65 "--output", 

66 type=str, 

67 metavar="file-name", 

68 help="Set output file.", 

69 cls=cmd_util.ApioOption, 

70) 

71 

72 

73def write_as_json_doc(top_dict: Dict, output_flag: str, force_flag: bool): 

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

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

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

77 

78 if output_flag: 

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

80 output_path = Path(output_flag) 

81 

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

83 cerror(f"The output path {output_path} is a directory.") 

84 sys.exit(1) 

85 

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

87 cerror(f"The file already exists {output_path}.") 

88 cout("Use the --force option to allow overwriting.", style=INFO) 

89 sys.exit(1) 

90 

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

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

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

94 dirname = os.path.dirname(output_flag) 

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

96 os.makedirs(dirname, exist_ok=True) 

97 

98 # -- Write to file. 

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

100 f.write(text) 

101 else: 

102 # -- Output the json text to stdout. 

103 print(text, file=sys.stdout) 

104 

105 

106# ------ apio api get-system 

107 

108 

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

110APIO_API_GET_SYSTEM_HELP = """ 

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

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

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

114 

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

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

117was indeed was generated by the same invocation. 

118 

119Examples:[code] 

120 apio api get-system # Write to stdout 

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

122""" 

123 

124 

125@click.command( 

126 name="get-system", 

127 cls=ApioCommand, 

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

129 help=APIO_API_GET_SYSTEM_HELP, 

130) 

131# @click.pass_context 

132@timestamp_option 

133@output_option 

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

135def _get_system_cli( 

136 *, 

137 # Options 

138 timestamp: str, 

139 output: str, 

140 force: bool, 

141): 

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

143 

144 apio_ctx = ApioContext( 

145 project_policy=ProjectPolicy.NO_PROJECT, 

146 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

147 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

148 ) 

149 

150 platform = apio_ctx.platform 

151 

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

153 top_dict = {} 

154 

155 # -- Append user timestamp if specified. 

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

157 top_dict["timestamp"] = timestamp 

158 

159 section_dict = {} 

160 

161 # -- Add fields. 

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

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

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

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

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

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

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

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

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

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

172 section_dict["vscode-debugger"] = str( 

173 util.is_under_vscode_debugger() 

174 ).lower() 

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

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

177 util.get_path_in_apio_package("") 

178 ) 

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

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

181 section_dict["remote-config-url"] = apio_ctx.profile.remote_config_url 

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

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

184 ) 

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

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

187 ) 

188 

189 # -- Add section 

190 top_dict["system"] = section_dict 

191 

192 # -- Write out 

193 write_as_json_doc(top_dict, output, force) 

194 

195 

196# ------ apio api get-build-report 

197 

198 

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

200APIO_API_GET_BUILD_REPORT_HELP = """ 

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

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

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

204the project. 

205 

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

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

208was indeed was generated by the same invocation. 

209 

210Examples:[code] 

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

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

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

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

215""" 

216 

217 

218@click.command( 

219 name="get-build-report", 

220 cls=ApioCommand, 

221 short_help="Get project build information.", 

222 help=APIO_API_GET_BUILD_REPORT_HELP, 

223) 

224# @click.pass_context 

225@options.env_option_gen() 

226@options.project_dir_option 

227@timestamp_option 

228@output_option 

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

230def _get_build_report_cli( 

231 *, 

232 # Options 

233 env: str, 

234 project_dir: Optional[Path], 

235 timestamp: str, 

236 output: str, 

237 force: bool, 

238): 

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

240 

241 apio_ctx = ApioContext( 

242 project_policy=ProjectPolicy.PROJECT_REQUIRED, 

243 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

244 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

245 project_dir_arg=project_dir, 

246 env_arg=env, 

247 report_env=False, 

248 ) 

249 

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

251 os.chdir(apio_ctx.project_dir) 

252 

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

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

255 

256 # -- Read the report 

257 report = build_report.read_build_report(pnr_json_file) 

258 

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

260 top_dict = {} 

261 

262 # -- Append user timestamp if specified. 

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

264 top_dict["timestamp"] = timestamp 

265 

266 section_dict = {} 

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

268 

269 resources_dict = {} 

270 for res in report.resources: 

271 resources_dict[res.name] = { 

272 "used": res.used, 

273 "available": res.available, 

274 "percentage": res.percentage, 

275 } 

276 

277 section_dict["resources"] = resources_dict 

278 

279 clocks_dict = {} 

280 for clk in report.clocks: 

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

282 

283 section_dict["clocks"] = clocks_dict 

284 

285 # -- Add section 

286 top_dict["build-report"] = section_dict 

287 

288 # -- Write out 

289 write_as_json_doc(top_dict, output, force) 

290 

291 

292# ------ apio api get-project 

293 

294 

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

296APIO_API_GET_PROJECT_HELP = """ 

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

298project as a JSON foc. 

299 

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

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

302was indeed was generated by the same invocation. 

303 

304Examples:[code] 

305 apio api get-project # Report default env 

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

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

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

309""" 

310 

311 

312@click.command( 

313 name="get-project", 

314 cls=ApioCommand, 

315 short_help="Get project information.", 

316 help=APIO_API_GET_PROJECT_HELP, 

317) 

318# @click.pass_context 

319@options.env_option_gen() 

320@options.project_dir_option 

321@timestamp_option 

322@output_option 

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

324def _get_project_cli( 

325 *, 

326 # Options 

327 env: str, 

328 project_dir: Optional[Path], 

329 timestamp: str, 

330 output: str, 

331 force: bool, 

332): 

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

334 

335 apio_ctx = ApioContext( 

336 project_policy=ProjectPolicy.PROJECT_REQUIRED, 

337 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

338 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

339 project_dir_arg=project_dir, 

340 env_arg=env, 

341 report_env=False, 

342 ) 

343 

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

345 os.chdir(apio_ctx.project_dir) 

346 

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

348 top_dict = {} 

349 

350 # -- Append user timestamp if specified. 

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

352 top_dict["timestamp"] = timestamp 

353 

354 section_dict = {} 

355 

356 active_env_dict = {} 

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

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

359 section_dict["active-env"] = active_env_dict 

360 

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

362 

363 synth_srcs, test_srcs = get_project_source_files() 

364 section_dict["synth-files"] = synth_srcs 

365 section_dict["test-benches"] = test_srcs 

366 

367 pr = apio_ctx.project_resources 

368 

369 board_dict = {"id": pr.board_id} 

370 board_dict.update(pr.board_info) 

371 section_dict["board"] = board_dict 

372 

373 fpga_dict = {"id": pr.fpga_id} 

374 fpga_dict.update(pr.fpga_info) 

375 section_dict["fpga"] = fpga_dict 

376 

377 programmer_dict = {"id": pr.programmer_id} 

378 programmer_dict.update(pr.programmer_info) 

379 section_dict["programmer"] = programmer_dict 

380 

381 # -- Add section 

382 top_dict["project"] = section_dict 

383 

384 # -- Write out 

385 write_as_json_doc(top_dict, output, force) 

386 

387 

388# ------ apio api get-boards 

389 

390 

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

392APIO_API_GET_BOARDS_HELP = """ 

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

394JSON document. 

395 

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

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

398was indeed was generated by the same invocation. 

399 

400Examples:[code] 

401 apio api get-boards # Write to stdout 

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

403""" 

404 

405 

406@click.command( 

407 name="get-boards", 

408 cls=ApioCommand, 

409 short_help="Retrieve boards information.", 

410 help=APIO_API_GET_BOARDS_HELP, 

411) 

412@timestamp_option 

413@output_option 

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

415def _get_boards_cli( 

416 *, 

417 # Options 

418 timestamp: str, 

419 output: str, 

420 force: bool, 

421): 

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

423 

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

425 # -- change in the future. 

426 apio_ctx = ApioContext( 

427 project_policy=ProjectPolicy.NO_PROJECT, 

428 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

429 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

430 ) 

431 

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

433 top_dict = {} 

434 

435 # -- Append user timestamp if specified. 

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

437 top_dict["timestamp"] = timestamp 

438 

439 # -- Generate the boards section. 

440 section = {} 

441 for board_id, board_info in apio_ctx.boards.items(): 

442 # -- The board output dict. 

443 board_dict = {} 

444 

445 # -- Add board description 

446 board_dict["description"] = board_info.get("description", None) 

447 

448 # -- Add board's fpga information. 

449 fpga_id = board_info.get("fpga-id", None) 

450 fpga_info = apio_ctx.fpgas.get(fpga_id, {}) 

451 assert "id" not in fpga_info 

452 fpga_dict = {"id": fpga_id} 

453 fpga_dict.update(fpga_info) 

454 board_dict["fpga"] = fpga_dict 

455 

456 # -- Add board's programmer information. 

457 programmer_dict = {} 

458 programmer_id = board_info.get("programmer", {}).get("id", None) 

459 programmer_dict["id"] = programmer_id 

460 board_dict["programmer"] = programmer_dict 

461 

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

463 section[board_id] = board_dict 

464 

465 top_dict["boards"] = section 

466 

467 # -- Write out 

468 write_as_json_doc(top_dict, output, force) 

469 

470 

471# ------ apio api get-fpgas 

472 

473 

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

475APIO_API_GET_FPGAS_HELP = """ 

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

477JSON document. 

478 

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

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

481was indeed was generated by the same invocation. 

482 

483Examples:[code] 

484 apio api get-fpgas # Write to stdout 

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

486""" 

487 

488 

489@click.command( 

490 name="get-fpgas", 

491 cls=ApioCommand, 

492 short_help="Retrieve FPGAs information.", 

493 help=APIO_API_GET_FPGAS_HELP, 

494) 

495@timestamp_option 

496@output_option 

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

498def _get_fpgas_cli( 

499 *, 

500 # Options 

501 timestamp: str, 

502 output: str, 

503 force: bool, 

504): 

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

506 

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

508 # -- change in the future. 

509 apio_ctx = ApioContext( 

510 project_policy=ProjectPolicy.NO_PROJECT, 

511 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

512 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

513 ) 

514 

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

516 top_dict = {} 

517 

518 # -- Append user timestamp if specified. 

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

520 top_dict["timestamp"] = timestamp 

521 

522 # -- Generate the fpgas section 

523 section = {} 

524 for fpga_id, fpga_info in apio_ctx.fpgas.items(): 

525 section[fpga_id] = fpga_info 

526 

527 top_dict["fpgas"] = section 

528 

529 # -- Write out 

530 write_as_json_doc(top_dict, output, force) 

531 

532 

533# ------ apio api get-programmers 

534 

535 

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

537APIO_API_GET_PROGRAMMERS_HELP = """ 

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

539as a JSON document. 

540 

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

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

543was indeed was generated by the same invocation. 

544 

545Examples:[code] 

546 apio api get-programmers # Write to stdout 

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

548""" 

549 

550 

551@click.command( 

552 name="get-programmers", 

553 cls=ApioCommand, 

554 short_help="Retrieve programmers information.", 

555 help=APIO_API_GET_PROGRAMMERS_HELP, 

556) 

557@timestamp_option 

558@output_option 

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

560def _get_programmers_cli( 

561 *, 

562 # Options 

563 timestamp: str, 

564 output: str, 

565 force: bool, 

566): 

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

568 

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

570 # -- change in the future. 

571 apio_ctx = ApioContext( 

572 project_policy=ProjectPolicy.NO_PROJECT, 

573 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

574 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

575 ) 

576 

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

578 top_dict = {} 

579 

580 # -- Append user timestamp if specified. 

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

582 top_dict["timestamp"] = timestamp 

583 

584 # -- Generate the 'programmers' section. 

585 section = {} 

586 for programmer_id, programmer_info in apio_ctx.programmers.items(): 

587 section[programmer_id] = programmer_info 

588 

589 top_dict["programmers"] = section 

590 

591 # -- Write out 

592 write_as_json_doc(top_dict, output, force) 

593 

594 

595# ------ apio api get-examples 

596 

597 

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

599APIO_API_GET_EXAMPLES_HELP = """ 

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

601JSON document. 

602 

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

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

605was indeed was generated by the same invocation. 

606 

607Examples:[code] 

608 apio api get-examples # Write to stdout 

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

610""" 

611 

612 

613@click.command( 

614 name="get-examples", 

615 cls=ApioCommand, 

616 short_help="Retrieve examples information.", 

617 help=APIO_API_GET_EXAMPLES_HELP, 

618) 

619@timestamp_option 

620@output_option 

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

622def _get_examples_cli( 

623 *, 

624 # Options 

625 timestamp: str, 

626 output: str, 

627 force: bool, 

628): 

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

630 

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

632 # -- change in the future. 

633 apio_ctx = ApioContext( 

634 project_policy=ProjectPolicy.NO_PROJECT, 

635 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

636 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

637 ) 

638 

639 # -- Get examples infos. 

640 examples: List[ExampleInfo] = Examples(apio_ctx).get_examples_infos() 

641 

642 # -- Group examples by boards 

643 boards_examples: Dict[str, List[ExampleInfo]] = {} 

644 for example in examples: 

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

646 board_examples.append(example) 

647 boards_examples[example.board_id] = board_examples 

648 

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

650 top_dict = {} 

651 

652 # -- Append user timestamp if specified. 

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

654 top_dict["timestamp"] = timestamp 

655 

656 # -- Generate the 'examples' section. 

657 section = {} 

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

659 board_dict = {} 

660 # -- Generate board examples 

661 for example_info in board_examples: 

662 example_dict = {} 

663 example_dict["description"] = example_info.description 

664 board_dict[example_info.example_name] = example_dict 

665 

666 section[board] = board_dict 

667 

668 top_dict["examples"] = section 

669 

670 # -- Write out 

671 write_as_json_doc(top_dict, output, force) 

672 

673 

674# ------ apio api get-commands 

675 

676 

677@dataclass(frozen=True) 

678class CmdInfo: 

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

680 

681 name: str 

682 path: List[str] 

683 cli: click.Command 

684 children: List[Self] 

685 

686 

687def scan_children(cmd_cli) -> Dict: 

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

689 result = {} 

690 

691 # -- Sanity check 

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

693 

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

695 if isinstance(cmd_cli, ApioCommand): 

696 return result 

697 

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

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

700 subgroups: List[ApioSubgroup] = cmd_cli.subgroups 

701 

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

703 subcommands_dict = {} 

704 result["commands"] = subcommands_dict 

705 

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

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

708 for subgroup in subgroups: 

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

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

711 for subcommand in subgroup.commands: 

712 subcommand_dict = scan_children(subcommand) 

713 subcommands_dict[subcommand.name] = subcommand_dict 

714 

715 # -- All done ok. 

716 return result 

717 

718 

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

720APIO_API_GET_COMMANDS_HELP = """ 

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

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

723documentation generators and tests. 

724 

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

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

727was indeed was generated by the same invocation. 

728 

729Examples:[code] 

730 apio api get-commands # Write to stdout 

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

732""" 

733 

734 

735@click.command( 

736 name="get-commands", 

737 cls=ApioCommand, 

738 short_help="Retrieve apio commands information.", 

739 help=APIO_API_GET_COMMANDS_HELP, 

740) 

741@click.pass_context 

742@timestamp_option 

743@output_option 

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

745def _get_commands_cli( 

746 # Click context 

747 cmd_ctx: click.Context, 

748 *, 

749 # Options 

750 timestamp: str, 

751 output: str, 

752 force: bool, 

753): 

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

755 

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

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

758 ctx = cast(ApioCmdContext, cmd_ctx) 

759 while ctx.parent: 

760 ctx = ctx.parent 

761 assert isinstance(ctx, ApioCmdContext), type(ctx) 

762 top_cli = ctx.command 

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

764 

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

766 ApioContext( 

767 project_policy=ProjectPolicy.NO_PROJECT, 

768 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

769 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

770 ) 

771 

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

773 top_dict = {} 

774 

775 # -- Append user timestamp if specified. 

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

777 top_dict["timestamp"] = timestamp 

778 

779 section_dict = {} 

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

781 top_dict["commands"] = section_dict 

782 

783 # -- Write out 

784 write_as_json_doc(top_dict, output, force) 

785 

786 

787# ------ apio api scan-devices 

788 

789 

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

791APIO_API_SCAN_DEVICES_HELP = """ 

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

793serial devices. 

794 

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

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

797was indeed was generated by the same invocation. 

798 

799Examples:[code] 

800 apio api scan-devices # Write to stdout 

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

802""" 

803 

804 

805@click.command( 

806 name="scan-devices", 

807 cls=ApioCommand, 

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

809 help=APIO_API_SCAN_DEVICES_HELP, 

810) 

811@timestamp_option 

812@output_option 

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

814def _scan_devices_cli( 

815 *, 

816 # Options 

817 timestamp: str, 

818 output: str, 

819 force: bool, 

820): 

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

822 

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

824 # -- change in the future. We need the config since we use libusb from 

825 # -- the packages. 

826 apio_ctx = ApioContext( 

827 project_policy=ProjectPolicy.NO_PROJECT, 

828 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

829 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

830 ) 

831 

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

833 top_dict = {} 

834 

835 # -- Append user timestamp if specified. 

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

837 top_dict["timestamp"] = timestamp 

838 

839 # -- We need the packages for the 'libusb' backend. 

840 # packages.install_missing_packages_on_the_fly(apio_ctx.packages_context) 

841 

842 usb_devices: List[UsbDevice] = usb_util.scan_usb_devices(apio_ctx) 

843 

844 # -- Scan and report usb devices. 

845 section = [] 

846 for device in usb_devices: 846 ↛ 847line 846 didn't jump to line 847 because the loop on line 846 never started

847 dev = {} 

848 dev["vid"] = device.vendor_id 

849 dev["pid"] = device.product_id 

850 dev["bus"] = device.bus 

851 dev["device"] = device.device 

852 dev["manufacturer"] = device.manufacturer 

853 dev["product"] = device.product 

854 dev["serial-number"] = device.serial_number 

855 dev["device_type"] = device.device_type 

856 

857 section.append(dev) 

858 

859 top_dict["usb-devices"] = section 

860 

861 # -- Scan and report serial devices. 

862 serial_devices: List[SerialDevice] = serial_util.scan_serial_devices( 

863 apio_ctx 

864 ) 

865 

866 section = [] 

867 for device in serial_devices: 867 ↛ 868line 867 didn't jump to line 868 because the loop on line 867 never started

868 dev = {} 

869 dev["port"] = device.port 

870 dev["port-name"] = device.port_name 

871 dev["vendor-id"] = device.vendor_id 

872 dev["product-id"] = device.product_id 

873 dev["manufacturer"] = device.manufacturer 

874 dev["product"] = device.product 

875 dev["serial-number"] = device.serial_number 

876 dev["device-type"] = device.device_type 

877 

878 section.append(dev) 

879 

880 top_dict["serial-devices"] = section 

881 

882 # -- Write out 

883 write_as_json_doc(top_dict, output, force) 

884 

885 

886# ------ apio api echo 

887 

888 

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

890APIO_API_ECHO_HELP = """ 

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

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

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

894 

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

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

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

898 

899Examples:[code] 

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

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

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

903""" 

904 

905# -- Supported style names 

906STYLES = { 

907 "OK": SUCCESS, 

908 "INFO": INFO, 

909 "WARNING": WARNING, 

910 "ERROR": ERROR, 

911 "TITLE": TITLE, 

912 "EMPH1": EMPH1, 

913 "EMPH2": EMPH2, 

914 "EMPH3": EMPH3, 

915} 

916 

917text_option = click.option( 

918 "text", # Var name. 

919 "-t", 

920 "--text", 

921 type=str, 

922 metavar="MESSAGE", 

923 required=True, 

924 help="Set message to echo.", 

925 cls=cmd_util.ApioOption, 

926) 

927 

928 

929style_option = click.option( 

930 "style", # Var name. 

931 "-s", 

932 "--style", 

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

934 metavar="STYLE", 

935 required=True, 

936 help="Set style to use.", 

937 cls=cmd_util.ApioOption, 

938) 

939 

940 

941@click.command( 

942 name="echo", 

943 cls=ApioCommand, 

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

945 help=APIO_API_ECHO_HELP, 

946) 

947@text_option 

948@style_option 

949def _echo_cli( 

950 *, 

951 # Options 

952 text: str, 

953 style: str, 

954): 

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

956 

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

958 _ = ApioContext( 

959 project_policy=ProjectPolicy.NO_PROJECT, 

960 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

961 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

962 ) 

963 

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

965 

966 

967# ------ apio apio 

968 

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

970APIO_API_HELP = """ 

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

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

973directly by users. 

974""" 

975 

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

977SUBGROUPS = [ 

978 ApioSubgroup( 

979 "Subcommands", 

980 [ 

981 _get_system_cli, 

982 _get_project_cli, 

983 _get_build_report_cli, 

984 _get_boards_cli, 

985 _get_fpgas_cli, 

986 _get_programmers_cli, 

987 _get_examples_cli, 

988 _get_commands_cli, 

989 _scan_devices_cli, 

990 _echo_cli, 

991 ], 

992 ) 

993] 

994 

995 

996@click.command( 

997 name="api", 

998 cls=ApioGroup, 

999 subgroups=SUBGROUPS, 

1000 short_help="Apio programmatic interface.", 

1001 help=APIO_API_HELP, 

1002) 

1003def cli(): 

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

1005 

1006 # pass