1# Copyright 2022 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14"""pw_ide CLI command handlers.""" 15 16import logging 17from pathlib import Path 18import shlex 19import shutil 20import subprocess 21import sys 22from typing import cast, Callable, List, Optional, Set, Tuple, Union 23 24from pw_cli.color import colors 25 26from pw_ide.cpp import ( 27 ClangdSettings, 28 compdb_generate_file_path, 29 CppCompilationDatabase, 30 CppCompilationDatabasesMap, 31 CppIdeFeaturesState, 32 delete_compilation_databases, 33 delete_compilation_database_caches, 34 MAX_COMMANDS_TARGET_FILENAME, 35) 36 37from pw_ide.exceptions import ( 38 BadCompDbException, 39 InvalidTargetException, 40 MissingCompDbException, 41) 42 43from pw_ide.python import PythonPaths 44 45from pw_ide.settings import ( 46 PigweedIdeSettings, 47 SupportedEditor, 48 SupportedEditorName, 49) 50 51from pw_ide import vscode 52from pw_ide.vscode import VscSettingsManager, VscSettingsType 53 54 55def _no_color(msg: str) -> str: 56 return msg 57 58 59def _split_lines(msg: Union[str, List[str]]) -> Tuple[str, List[str]]: 60 """Turn a list of strings into a tuple of the first and list of rest.""" 61 if isinstance(msg, str): 62 return (msg, []) 63 64 return (msg[0], msg[1:]) 65 66 67class StatusReporter: 68 """Print user-friendly status reports to the terminal for CLI tools. 69 70 The output of ``demo()`` looks something like this, but more colorful: 71 72 .. code-block:: none 73 74 • FYI, here's some information: 75 Lorem ipsum dolor sit amet, consectetur adipiscing elit. 76 Donec condimentum metus molestie metus maximus ultricies ac id dolor. 77 ✓ This is okay, no changes needed. 78 ✓ We changed some things successfully! 79 ⚠ Uh oh, you might want to be aware of this. 80 ❌ This is bad! Things might be broken! 81 82 You can instead redirect these lines to logs without formatting by 83 substituting ``LoggingStatusReporter``. Consumers of this should be 84 designed to take any subclass and not make assumptions about where the 85 output will go. But the reason you would choose this over plain logging is 86 because you want to support pretty-printing to the terminal. 87 88 This is also "themable" in the sense that you can subclass this, override 89 the methods with whatever formatting you want, and supply the subclass to 90 anything that expects an instance of this. 91 92 Key: 93 94 - info: Plain ol' informational status. 95 - ok: Something was checked and it was okay. 96 - new: Something needed to be changed/updated and it was successfully. 97 - wrn: Warning, non-critical. 98 - err: Error, critical. 99 100 This doesn't expose the %-style string formatting that is used in idiomatic 101 Python logging, but this shouldn't be used for performance-critical logging 102 situations anyway. 103 """ 104 105 def _report( # pylint: disable=no-self-use 106 self, 107 msg: Union[str, List[str]], 108 color: Callable[[str], str], 109 char: str, 110 func: Callable, 111 silent: bool, 112 ) -> None: 113 """Actually print/log/whatever the status lines.""" 114 first_line, rest_lines = _split_lines(msg) 115 first_line = color(f'{char} {first_line}') 116 spaces = ' ' * len(char) 117 rest_lines = [color(f'{spaces} {line}') for line in rest_lines] 118 119 if not silent: 120 for line in [first_line, *rest_lines]: 121 func(line) 122 123 def demo(self): 124 """Run this to see what your status reporter output looks like.""" 125 self.info( 126 [ 127 'FYI, here\'s some information:', 128 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 129 'Donec condimentum metus molestie metus maximus ultricies ' 130 'ac id dolor.', 131 ] 132 ) 133 self.ok('This is okay, no changes needed.') 134 self.new('We changed some things successfully!') 135 self.wrn('Uh oh, you might want to be aware of this.') 136 self.err('This is bad! Things might be broken!') 137 138 def info(self, msg: Union[str, List[str]], silent: bool = False) -> None: 139 self._report(msg, _no_color, '\u2022', print, silent) 140 141 def ok(self, msg: Union[str, List[str]], silent: bool = False) -> None: 142 self._report(msg, colors().blue, '\u2713', print, silent) 143 144 def new(self, msg: Union[str, List[str]], silent: bool = False) -> None: 145 self._report(msg, colors().green, '\u2713', print, silent) 146 147 def wrn(self, msg: Union[str, List[str]], silent: bool = False) -> None: 148 self._report(msg, colors().yellow, '\u26A0', print, silent) 149 150 def err(self, msg: Union[str, List[str]], silent: bool = False) -> None: 151 self._report(msg, colors().red, '\u274C', print, silent) 152 153 154class LoggingStatusReporter(StatusReporter): 155 """Print status lines to logs instead of to the terminal.""" 156 157 def __init__(self, logger: logging.Logger) -> None: 158 self.logger = logger 159 super().__init__() 160 161 def _report( 162 self, 163 msg: Union[str, List[str]], 164 color: Callable[[str], str], 165 char: str, 166 func: Callable, 167 silent: bool, 168 ) -> None: 169 first_line, rest_lines = _split_lines(msg) 170 171 if not silent: 172 for line in [first_line, *rest_lines]: 173 func(line) 174 175 def info(self, msg: Union[str, List[str]], silent: bool = False) -> None: 176 self._report(msg, _no_color, '', self.logger.info, silent) 177 178 def ok(self, msg: Union[str, List[str]], silent: bool = False) -> None: 179 self._report(msg, _no_color, '', self.logger.info, silent) 180 181 def new(self, msg: Union[str, List[str]], silent: bool = False) -> None: 182 self._report(msg, _no_color, '', self.logger.info, silent) 183 184 def wrn(self, msg: Union[str, List[str]], silent: bool = False) -> None: 185 self._report(msg, _no_color, '', self.logger.warning, silent) 186 187 def err(self, msg: Union[str, List[str]], silent: bool = False) -> None: 188 self._report(msg, _no_color, '', self.logger.error, silent) 189 190 191def _make_working_dir( 192 reporter: StatusReporter, settings: PigweedIdeSettings, quiet: bool = False 193) -> None: 194 if not settings.working_dir.exists(): 195 settings.working_dir.mkdir() 196 reporter.new( 197 'Initialized the Pigweed IDE working directory at ' 198 f'{settings.working_dir}' 199 ) 200 else: 201 if not quiet: 202 reporter.ok( 203 'Pigweed IDE working directory already present at ' 204 f'{settings.working_dir}' 205 ) 206 207 208def _report_unrecognized_editor(reporter: StatusReporter, editor: str) -> None: 209 supported_editors = ', '.join(sorted([ed.value for ed in SupportedEditor])) 210 reporter.wrn(f'Unrecognized editor: {editor}') 211 reporter.wrn('This may not be an automatically-supported editor.') 212 reporter.wrn(f'Automatically-supported editors: {supported_editors}') 213 214 215def cmd_clear( 216 compdb: bool, 217 cache: bool, 218 editor: Optional[SupportedEditorName], 219 editor_backups: Optional[SupportedEditorName], 220 silent: bool = False, 221 reporter: StatusReporter = StatusReporter(), 222 pw_ide_settings: PigweedIdeSettings = PigweedIdeSettings(), 223) -> None: 224 """Clear components of the IDE features. 225 226 In contrast to the ``reset`` subcommand, ``clear`` allows you to specify 227 components to delete. You will not need this command under normal 228 circumstances. 229 """ 230 if compdb: 231 delete_compilation_databases(pw_ide_settings) 232 reporter.wrn('Cleared compilation databases', silent) 233 234 if cache: 235 delete_compilation_database_caches(pw_ide_settings) 236 reporter.wrn('Cleared compilation database caches', silent) 237 238 if editor is not None: 239 try: 240 validated_editor = SupportedEditor(editor) 241 except ValueError: 242 _report_unrecognized_editor(reporter, cast(str, editor)) 243 sys.exit(1) 244 245 if validated_editor == SupportedEditor.VSCODE: 246 vsc_settings_manager = VscSettingsManager(pw_ide_settings) 247 vsc_settings_manager.delete_all_active_settings() 248 249 reporter.wrn( 250 f'Cleared active settings for {validated_editor.value}', silent 251 ) 252 253 if editor_backups is not None: 254 try: 255 validated_editor = SupportedEditor(editor_backups) 256 except ValueError: 257 _report_unrecognized_editor(reporter, cast(str, editor)) 258 sys.exit(1) 259 260 if validated_editor == SupportedEditor.VSCODE: 261 vsc_settings_manager = VscSettingsManager(pw_ide_settings) 262 vsc_settings_manager.delete_all_backups() 263 264 reporter.wrn( 265 f'Cleared backup settings for {validated_editor.value}', 266 silent=silent, 267 ) 268 269 270def cmd_reset( 271 hard: bool = False, 272 reporter: StatusReporter = StatusReporter(), 273 pw_ide_settings: PigweedIdeSettings = PigweedIdeSettings(), 274) -> None: 275 """Reset IDE settings. 276 277 This will clear your .pw_ide working directory and active settings for 278 supported editors, restoring your repository to a pre-"pw ide setup" state. 279 Any clangd caches in the working directory will not be removed, so that they 280 don't need to be generated again later. All backed up supported editor 281 settings will also be left in place. 282 283 Adding the --hard flag will completely delete the .pw_ide directory and all 284 supported editor backup settings, restoring your repository to a 285 pre-`pw ide setup` state. 286 287 This command does not affect this project's pw_ide and editor settings or 288 your own pw_ide and editor override settings. 289 """ 290 delete_compilation_databases(pw_ide_settings) 291 vsc_settings_manager = VscSettingsManager(pw_ide_settings) 292 vsc_settings_manager.delete_all_active_settings() 293 294 if hard: 295 try: 296 shutil.rmtree(pw_ide_settings.working_dir) 297 except FileNotFoundError: 298 pass 299 300 vsc_settings_manager.delete_all_backups() 301 302 reporter.wrn('Pigweed IDE settings were reset!') 303 304 305def cmd_setup( 306 reporter: StatusReporter = StatusReporter(), 307 pw_ide_settings: PigweedIdeSettings = PigweedIdeSettings(), 308) -> None: 309 """Set up or update your Pigweed project IDE features. 310 311 This will automatically set up your development environment with all the 312 features that Pigweed IDE supports, with sensible defaults. 313 314 At minimum, this command will create the .pw_ide working directory and 315 create settings files for all supported editors. Projects can define 316 additional setup steps in .pw_ide.yaml. 317 318 When new IDE features are introduced in the future (either by Pigweed or 319 your downstream project), you can re-run this command to set up the new 320 features. It will not overwrite or break any of your existing configuration. 321 """ 322 _make_working_dir(reporter, pw_ide_settings) 323 324 if pw_ide_settings.editor_enabled('vscode'): 325 cmd_vscode() 326 327 for command in pw_ide_settings.setup: 328 subprocess.run(shlex.split(command)) 329 330 331def cmd_vscode( 332 include: Optional[List[VscSettingsType]] = None, 333 exclude: Optional[List[VscSettingsType]] = None, 334 no_override: bool = False, 335 reporter: StatusReporter = StatusReporter(), 336 pw_ide_settings: PigweedIdeSettings = PigweedIdeSettings(), 337) -> None: 338 """Configure support for Visual Studio Code. 339 340 This will replace your current Visual Studio Code (VSC) settings for this 341 project (in ``.vscode/settings.json``, etc.) with the following sets of 342 settings, in order: 343 344 - The Pigweed default settings 345 - Your project's settings, if any (in ``.vscode/pw_project_settings.json``) 346 - Your personal settings, if any (in ``.vscode/pw_user_settings.json``) 347 348 In other words, settings files lower on the list can override settings 349 defined by those higher on the list. Settings defined in the sources above 350 are not active in VSC until they are merged and output to the current 351 settings file by running: 352 353 .. code-block:: bash 354 355 pw ide vscode 356 357 Refer to the Visual Studio Code documentation for more information about 358 these settings: https://code.visualstudio.com/docs/getstarted/settings 359 360 This command also manages VSC tasks (``.vscode/tasks.json``) and extensions 361 (``.vscode/extensions.json``). You can explicitly control which of these 362 settings types ("settings", "tasks", and "extensions") is modified by 363 this command by using the ``--include`` or ``--exclude`` options. 364 365 Your current VSC settings will never change unless you run ``pw ide`` 366 commands. Since the current VSC settings are an artifact built from the 367 three settings files described above, you should avoid manually editing 368 that file; it will be replaced the next time you run ``pw ide vscode``. A 369 backup of your previous settings file will be made, and you can diff it 370 against the new file to see what changed. 371 372 These commands will never modify your VSC user settings, which are 373 stored outside of the project repository and apply globally to all VSC 374 instances. 375 376 The settings files are system-specific and shouldn't be checked into the 377 repository, except for the project settings (those with ``pw_project_``), 378 which can be used to define consistent settings for everyone working on the 379 project. 380 381 Note that support for VSC can be disabled at the project level or the user 382 level by adding the following to .pw_ide.yaml or .pw_ide.user.yaml 383 respectively: 384 385 .. code-block:: yaml 386 387 editors: 388 vscode: false 389 390 Likewise, it can be enabled by setting that value to true. It is enabled by 391 default. 392 """ 393 if not pw_ide_settings.editor_enabled('vscode'): 394 reporter.wrn('Visual Studio Code support is disabled in settings!') 395 sys.exit(1) 396 397 if not vscode.DEFAULT_SETTINGS_PATH.exists(): 398 vscode.DEFAULT_SETTINGS_PATH.mkdir() 399 400 vsc_manager = VscSettingsManager(pw_ide_settings) 401 402 if include is None: 403 include_set = set(VscSettingsType.all()) 404 else: 405 include_set = set(include) 406 407 if exclude is None: 408 exclude_set: Set[VscSettingsType] = set() 409 else: 410 exclude_set = set(exclude) 411 412 types_to_update = cast( 413 List[VscSettingsType], tuple(include_set - exclude_set) 414 ) 415 416 for settings_type in types_to_update: 417 active_settings_existed = vsc_manager.active(settings_type).is_present() 418 419 if no_override and active_settings_existed: 420 reporter.ok( 421 f'Visual Studio Code active {settings_type.value} ' 422 'already present; will not overwrite' 423 ) 424 425 else: 426 with vsc_manager.active(settings_type).modify( 427 reinit=True 428 ) as active_settings: 429 vsc_manager.default(settings_type).sync_to(active_settings) 430 vsc_manager.project(settings_type).sync_to(active_settings) 431 vsc_manager.user(settings_type).sync_to(active_settings) 432 433 verb = 'Updated' if active_settings_existed else 'Created' 434 reporter.new( 435 f'{verb} Visual Studio Code active ' f'{settings_type.value}' 436 ) 437 438 439# TODO(chadnorvell): Break up this function. 440# The linting errors are a nuisance but they're beginning to have a point. 441def cmd_cpp( # pylint: disable=too-many-arguments, too-many-locals, too-many-branches, too-many-statements 442 should_list_targets: bool, 443 should_get_target: bool, 444 target_to_set: Optional[str], 445 compdb_file_paths: Optional[List[Path]], 446 build_dir: Optional[Path], 447 use_default_target: bool = False, 448 should_run_ninja: bool = False, 449 should_run_gn: bool = False, 450 override_current_target: bool = True, 451 clangd_command: bool = False, 452 clangd_command_system: Optional[str] = None, 453 reporter: StatusReporter = StatusReporter(), 454 pw_ide_settings: PigweedIdeSettings = PigweedIdeSettings(), 455) -> None: 456 """Configure C/C++ code intelligence support. 457 458 Code intelligence can be provided by clangd or other language servers that 459 use the clangd compilation database format, defined at: 460 https://clang.llvm.org/docs/JSONCompilationDatabase.html 461 462 This command helps you use clangd with Pigweed projects, which use multiple 463 toolchains within a distinct environment, and often define multiple targets. 464 This means compilation units are likely have multiple compile commands, and 465 clangd is not equipped to deal with this out of the box. We handle this by: 466 467 - Processing the compilation database produced the build system into 468 multiple internally-consistent compilation databases, one for each target 469 (where a "target" is a particular build for a particular system using a 470 particular toolchain). 471 472 - Providing commands to select which target you want to use for code 473 analysis. 474 475 Refer to the Pigweed documentation or your build system's documentation to 476 learn how to produce a clangd compilation database. Once you have one, run 477 this command to process it (or provide a glob to process multiple): 478 479 .. code-block:: bash 480 481 pw ide cpp --process {path to compile_commands.json} 482 483 If you're using GN to generate the compilation database, you can do that and 484 process it in a single command: 485 486 .. code-block:: bash 487 488 pw ide cpp --gn 489 490 You can do the same for a Ninja build (whether it was generated by GN or 491 another way): 492 493 .. code-block:: bash 494 495 pw ide cpp --ninja 496 497 You can now examine the targets that are available to you: 498 499 .. code-block:: bash 500 501 pw ide cpp --list 502 503 ... and select the target you want to use: 504 505 .. code-block:: bash 506 507 pw ide cpp --set host_clang 508 509 As long as your editor or language server plugin is properly configured, you 510 will now get code intelligence features relevant to that particular target. 511 512 You can see what target is selected by running: 513 514 .. code-block:: bash 515 516 pw ide cpp 517 518 Whenever you switch to a target you haven't used before, clangd will need to 519 index the build, which may take several minutes. These indexes are cached, 520 so you can switch between targets without re-indexing each time. 521 522 If your build configuration changes significantly (e.g. you add a new file 523 to the project), you will need to re-process the compilation database for 524 that change to be recognized. Your target selection will not change, and 525 your index will only need to be incrementally updated. 526 527 You can generate the clangd command your editor needs to run with: 528 529 .. code-block:: bash 530 531 pw ide cpp --clangd-command 532 533 If your editor uses JSON for configuration, you can export the same command 534 in that format: 535 536 .. code-block:: bash 537 538 pw ide cpp --clangd-command-for json 539 """ 540 _make_working_dir(reporter, pw_ide_settings, quiet=True) 541 542 # If true, no arguments were provided so we do the default behavior. 543 default = True 544 545 build_dir = ( 546 build_dir if build_dir is not None else pw_ide_settings.build_dir 547 ) 548 549 if compdb_file_paths is not None: 550 should_process = True 551 552 if len(compdb_file_paths) == 0: 553 compdb_file_paths = pw_ide_settings.compdb_paths_expanded 554 else: 555 should_process = False 556 # This simplifies typing in the rest of this method. We rely on 557 # `should_process` instead of the status of this variable. 558 compdb_file_paths = [] 559 560 # Order of operations matters from here on. It should be possible to run 561 # a build system command to generate a compilation database, then process 562 # the compilation database, then successfully set the target in a single 563 # command. 564 565 # Use Ninja to generate the initial compile_commands.json 566 if should_run_ninja: 567 default = False 568 569 ninja_commands = ['ninja', '-t', 'compdb'] 570 reporter.info(f'Running Ninja: {" ".join(ninja_commands)}') 571 572 output_compdb_file_path = build_dir / compdb_generate_file_path() 573 574 try: 575 # Ninja writes to STDOUT, so we capture to a file. 576 with open(output_compdb_file_path, 'w') as compdb_file: 577 result = subprocess.run( 578 ninja_commands, 579 cwd=build_dir, 580 stdout=compdb_file, 581 stderr=subprocess.PIPE, 582 ) 583 except FileNotFoundError: 584 reporter.err(f'Could not open path! {str(output_compdb_file_path)}') 585 586 if result.returncode == 0: 587 reporter.info('Ran Ninja successfully!') 588 should_process = True 589 compdb_file_paths.append(output_compdb_file_path) 590 else: 591 reporter.err('Something went wrong!') 592 # Convert from bytes and remove trailing newline 593 err = result.stderr.decode().split('\n')[:-1] 594 595 for line in err: 596 reporter.err(line) 597 598 sys.exit(1) 599 600 # Use GN to generate the initial compile_commands.json 601 if should_run_gn: 602 default = False 603 604 gn_commands = ['gn', 'gen', str(build_dir), '--export-compile-commands'] 605 606 try: 607 with open(build_dir / 'args.gn') as args_file: 608 gn_args = [ 609 line 610 for line in args_file.readlines() 611 if not line.startswith('#') 612 ] 613 except FileNotFoundError: 614 gn_args = [] 615 616 gn_args_string = 'none' if len(gn_args) == 0 else ', '.join(gn_args) 617 618 reporter.info( 619 [f'Running GN: {" ".join(gn_commands)} (args: {gn_args_string})'] 620 ) 621 622 result = subprocess.run(gn_commands, capture_output=True) 623 gn_status_lines = ['Ran GN successfully!'] 624 625 if result.returncode == 0: 626 # Convert from bytes and remove trailing newline 627 out = result.stdout.decode().split('\n')[:-1] 628 629 for line in out: 630 gn_status_lines.append(line) 631 632 reporter.info(gn_status_lines) 633 should_process = True 634 output_compdb_file_path = build_dir / compdb_generate_file_path() 635 compdb_file_paths.append(output_compdb_file_path) 636 else: 637 reporter.err('Something went wrong!') 638 # Convert from bytes and remove trailing newline 639 err = result.stderr.decode().split('\n')[:-1] 640 641 for line in err: 642 reporter.err(line) 643 644 sys.exit(1) 645 646 if should_process: 647 default = False 648 prev_targets = len(CppIdeFeaturesState(pw_ide_settings)) 649 compdb_databases: List[CppCompilationDatabasesMap] = [] 650 last_processed_path = Path() 651 652 for compdb_file_path in compdb_file_paths: 653 # If the path is a dir, append the default compile commands 654 # file name. 655 if compdb_file_path.is_dir(): 656 compdb_file_path /= compdb_generate_file_path() 657 658 try: 659 compdb_databases.append( 660 CppCompilationDatabase.load( 661 Path(compdb_file_path), build_dir 662 ).process( 663 settings=pw_ide_settings, 664 path_globs=pw_ide_settings.clangd_query_drivers(), 665 ) 666 ) 667 except MissingCompDbException: 668 reporter.err(f'File not found: {str(compdb_file_path)}') 669 670 if '*' in str(compdb_file_path): 671 reporter.wrn( 672 'It looks like you provided a glob that ' 673 'did not match any files.' 674 ) 675 676 sys.exit(1) 677 # TODO(chadnorvell): Recover more gracefully from errors. 678 except BadCompDbException: 679 reporter.err( 680 'File does not match compilation database format: ' 681 f'{str(compdb_file_path)}' 682 ) 683 sys.exit(1) 684 685 last_processed_path = compdb_file_path 686 687 if len(compdb_databases) == 0: 688 reporter.err( 689 'No compilation databases found in: ' 690 f'{str(compdb_file_paths)}' 691 ) 692 sys.exit(1) 693 694 try: 695 CppCompilationDatabasesMap.merge(*compdb_databases).write() 696 except TypeError: 697 reporter.err('Could not serialize file to JSON!') 698 699 total_targets = len(CppIdeFeaturesState(pw_ide_settings)) 700 new_targets = total_targets - prev_targets 701 702 if len(compdb_file_paths) == 1: 703 processed_text = str(last_processed_path) 704 else: 705 processed_text = f'{len(compdb_file_paths)} compilation databases' 706 707 reporter.new( 708 [ 709 f'Processed {processed_text} ' 710 f'to {pw_ide_settings.working_dir}', 711 f'{total_targets} targets are now available ' 712 f'({new_targets} are new)', 713 ] 714 ) 715 716 if use_default_target: 717 defined_default = pw_ide_settings.default_target 718 max_commands_target: Optional[str] = None 719 720 try: 721 with open( 722 pw_ide_settings.working_dir / MAX_COMMANDS_TARGET_FILENAME 723 ) as max_commands_target_file: 724 max_commands_target = max_commands_target_file.readline() 725 except FileNotFoundError: 726 pass 727 728 if defined_default is None and max_commands_target is None: 729 reporter.err('Can\'t use default target because none is defined!') 730 reporter.wrn('Have you processed a compilation database yet?') 731 sys.exit(1) 732 733 target_to_set = ( 734 defined_default 735 if defined_default is not None 736 else max_commands_target 737 ) 738 739 if target_to_set is not None: 740 default = False 741 742 # Always set the target if it's not already set, but if it is, 743 # respect the --no-override flag. 744 should_set_target = ( 745 CppIdeFeaturesState(pw_ide_settings).current_target is None 746 or override_current_target 747 ) 748 749 if should_set_target: 750 try: 751 CppIdeFeaturesState( 752 pw_ide_settings 753 ).current_target = target_to_set 754 except InvalidTargetException: 755 reporter.err( 756 [ 757 f'Invalid target! {target_to_set} not among the ' 758 'defined targets.', 759 'Check .pw_ide.yaml or .pw_ide.user.yaml for defined ' 760 'targets.', 761 ] 762 ) 763 sys.exit(1) 764 except MissingCompDbException: 765 reporter.err( 766 [ 767 f'File not found for target! {target_to_set}', 768 'Did you run pw ide cpp --process ' 769 '{path to compile_commands.json}?', 770 ] 771 ) 772 sys.exit(1) 773 774 reporter.new( 775 'Set C/C++ language server analysis target to: ' 776 f'{target_to_set}' 777 ) 778 else: 779 reporter.ok( 780 'Target already is set and will not be overridden: ' 781 f'{CppIdeFeaturesState(pw_ide_settings).current_target}' 782 ) 783 784 if clangd_command: 785 default = False 786 reporter.info( 787 [ 788 'Command to run clangd with Pigweed paths:', 789 ClangdSettings(pw_ide_settings).command(), 790 ] 791 ) 792 793 if clangd_command_system is not None: 794 default = False 795 reporter.info( 796 [ 797 'Command to run clangd with Pigweed paths for ' 798 f'{clangd_command_system}:', 799 ClangdSettings(pw_ide_settings).command(clangd_command_system), 800 ] 801 ) 802 803 if should_list_targets: 804 default = False 805 targets_list_status = [ 806 'C/C++ targets available for language server analysis:' 807 ] 808 809 for target in sorted( 810 CppIdeFeaturesState(pw_ide_settings).enabled_available_targets 811 ): 812 targets_list_status.append(f'\t{target}') 813 814 reporter.info(targets_list_status) 815 816 if should_get_target or default: 817 reporter.info( 818 'Current C/C++ language server analysis target: ' 819 f'{CppIdeFeaturesState(pw_ide_settings).current_target}' 820 ) 821 822 823def cmd_python( 824 should_print_venv: bool, reporter: StatusReporter = StatusReporter() 825) -> None: 826 """Configure Python code intelligence support. 827 828 You can generate the path to the Python virtual environment interpreter that 829 your editor/language server should use with: 830 831 .. code-block:: bash 832 833 pw ide python --venv 834 """ 835 # If true, no arguments were provided and we should do the default 836 # behavior. 837 default = True 838 839 if should_print_venv or default: 840 reporter.info( 841 [ 842 'Location of the Pigweed Python virtual environment:', 843 PythonPaths().interpreter, 844 ] 845 ) 846