1#!/usr/bin/env python3 2# 3# Copyright 2018 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""It is an AIDEGen sub task : IDE operation task! 18 19Takes a project file path as input, after passing the needed check(file 20existence, IDE type, etc.), launch the project in related IDE. 21 22 Typical usage example: 23 24 ide_util_obj = IdeUtil() 25 if ide_util_obj.is_ide_installed(): 26 ide_util_obj.config_ide() 27 ide_util_obj.launch_ide(project_file) 28""" 29 30import fnmatch 31import glob 32import logging 33import os 34import platform 35import subprocess 36 37from aidegen import constant 38from aidegen.lib.config import AidegenConfig 39 40# Add 'nohup' to prevent IDE from being terminated when console is terminated. 41_NOHUP = 'nohup' 42_IGNORE_STD_OUT_ERR_CMD = '2>/dev/null >&2' 43_IDEA_FOLDER = '.idea' 44_IML_EXTENSION = '.iml' 45_JDK_PATH_TOKEN = '@JDKpath' 46_TARGET_JDK_NAME_TAG = '<name value="JDK18" />' 47_COMPONENT_END_TAG = ' </component>' 48 49 50class IdeUtil(): 51 """Provide a set of IDE operations, e.g., launch and configuration. 52 53 Attributes: 54 _ide: IdeBase derived instance, the related IDE object. 55 56 For example: 57 1. Check if IDE is installed. 58 2. Launch an IDE. 59 3. Config IDE, e.g. config code style, SDK path, and etc. 60 """ 61 62 def __init__(self, 63 installed_path=None, 64 ide='j', 65 config_reset=False, 66 is_mac=False): 67 logging.debug('IdeUtil with OS name: %s%s', platform.system(), 68 '(Mac)' if is_mac else '') 69 self._ide = _get_ide(installed_path, ide, config_reset, is_mac) 70 71 def is_ide_installed(self): 72 """Checks if the IDE is already installed. 73 74 Returns: 75 True if IDE is installed already, otherwise False. 76 """ 77 return self._ide.is_ide_installed() 78 79 def launch_ide(self, project_file): 80 """Launches the relative IDE by opening the passed project file. 81 82 Args: 83 project_file: The full path of the IDE project file. 84 """ 85 return self._ide.launch_ide(project_file) 86 87 def config_ide(self): 88 """To config the IDE, e.g., setup code style, init SDK, and etc.""" 89 if self.is_ide_installed() and self._ide: 90 self._ide.apply_optional_config() 91 92 def get_default_path(self): 93 """Gets IDE default installed path.""" 94 return self._ide.default_installed_path 95 96 def ide_name(self): 97 """Gets IDE name.""" 98 return self._ide.ide_name 99 100 101class IdeBase(): 102 """The most base class of IDE, provides interface and partial path init. 103 104 Attributes: 105 _installed_path: String for the IDE binary path. 106 _config_reset: Boolean, True for reset configuration, else not reset. 107 _bin_file_name: String for IDE executable file name. 108 _bin_paths: A list of all possible IDE executable file absolute paths. 109 _ide_name: String for IDE name. 110 _bin_folders: A list of all possible IDE installed paths. 111 112 For example: 113 1. Check if IDE is installed. 114 2. Launch IDE. 115 3. Config IDE. 116 """ 117 118 def __init__(self, installed_path=None, config_reset=False): 119 self._installed_path = installed_path 120 self._config_reset = config_reset 121 self._ide_name = '' 122 self._bin_file_name = '' 123 self._bin_paths = [] 124 self._bin_folders = [] 125 126 def is_ide_installed(self): 127 """Checks if IDE is already installed. 128 129 Returns: 130 True if IDE is installed already, otherwise False. 131 """ 132 return bool(self._installed_path) 133 134 def launch_ide(self, project_file): 135 """Launches IDE by opening the passed project file. 136 137 Args: 138 project_file: The full path of the IDE's project file. 139 """ 140 _launch_ide(project_file, self._get_ide_cmd(project_file), 141 self._ide_name) 142 143 def apply_optional_config(self): 144 """Handles IDE relevant configs.""" 145 # Default does nothing, the derived classes know what need to config. 146 147 @property 148 def default_installed_path(self): 149 """Gets IDE default installed path.""" 150 return ' '.join(self._bin_folders) 151 152 @property 153 def ide_name(self): 154 """Gets IDE name.""" 155 return self._ide_name 156 157 def _get_ide_cmd(self, project_file): 158 """Compose launch IDE command to run a new process and redirect output. 159 160 Args: 161 project_file: The full path of the IDE's project file. 162 163 Returns: 164 A string of launch IDE command. 165 """ 166 return _get_run_ide_cmd(self._installed_path, project_file) 167 168 def _init_installed_path(self, installed_path): 169 """Initialize IDE installed path. 170 171 Args: 172 installed_path: the installed path to be checked. 173 """ 174 if installed_path: 175 self._installed_path = _get_script_from_input_path( 176 installed_path, self._bin_file_name) 177 else: 178 self._installed_path = self._get_script_from_system() 179 180 def _get_script_from_system(self): 181 """Get correct IDE installed path from internal path. 182 183 Returns: 184 The sh full path, or None if no IntelliJ version is installed. 185 """ 186 return _get_script_from_internal_path(self._bin_paths, self._ide_name) 187 188 def _get_possible_bin_paths(self): 189 """Gets all possible IDE installed paths.""" 190 return [os.path.join(f, self._bin_file_name) for f in self._bin_folders] 191 192 193class IdeIntelliJ(IdeBase): 194 """Provide basic IntelliJ ops, e.g., launch IDEA, and config IntelliJ. 195 196 Class Attributes: 197 _JDK_PATH: The path of JDK in android project. 198 _IDE_JDK_TABLE_PATH: The path of JDK table which record JDK info in IDE. 199 _JDK_PART_TEMPLATE_PATH: The path of the template of partial JDK table. 200 _JDK_FULL_TEMPLATE_PATH: The path of the template of full JDK table. 201 202 For example: 203 1. Check if IntelliJ is installed. 204 2. Launch an IntelliJ. 205 3. Config IntelliJ. 206 """ 207 208 _JDK_PATH = '' 209 _IDE_JDK_TABLE_PATH = '' 210 _JDK_PART_TEMPLATE_PATH = '' 211 _JDK_FULL_TEMPLATE_PATH = '' 212 213 def __init__(self, installed_path=None, config_reset=False): 214 super().__init__(installed_path, config_reset) 215 self._ide_name = constant.IDE_INTELLIJ 216 self._ls_ce_path = '' 217 self._ls_ue_path = '' 218 self._init_installed_path(installed_path) 219 220 def apply_optional_config(self): 221 """Do IDEA global config action. 222 223 Run code style config, SDK config. 224 """ 225 if not self._installed_path: 226 return 227 # Skip config action if there's no config folder exists. 228 _path_list = self._get_config_root_paths() 229 if not _path_list: 230 return 231 232 for _config_path in _path_list: 233 self._set_jdk_config(_config_path) 234 235 def _get_config_root_paths(self): 236 """Get the config root paths from derived class. 237 238 Returns: 239 A string list of IDE config paths, return multiple paths if more 240 than one path are found, return None if no path is found. 241 """ 242 raise NotImplementedError() 243 244 def _get_config_folder_name(self): 245 """Get the config sub folder name from derived class. 246 247 Returns: 248 A string of the sub path for the config folder. 249 """ 250 raise NotImplementedError('Method overriding is needed.') 251 252 def _set_jdk_config(self, path): 253 """Add jdk path to jdk.table.xml 254 255 Args: 256 path: The path of IntelliJ config path. 257 """ 258 jdk_table_path = os.path.join(path, self._IDE_JDK_TABLE_PATH) 259 try: 260 if os.path.isfile(jdk_table_path): 261 with open(jdk_table_path, 'r+') as jdk_table_fd: 262 jdk_table = jdk_table_fd.read() 263 jdk_table_fd.seek(0) 264 if _TARGET_JDK_NAME_TAG not in jdk_table: 265 with open(self._JDK_PART_TEMPLATE_PATH) as template_fd: 266 template = template_fd.read() 267 template = template.replace(_JDK_PATH_TOKEN, 268 self._JDK_PATH) 269 jdk_table = jdk_table.replace( 270 _COMPONENT_END_TAG, template) 271 jdk_table_fd.truncate() 272 jdk_table_fd.write(jdk_table) 273 else: 274 with open(self._JDK_FULL_TEMPLATE_PATH) as template_fd: 275 template = template_fd.read() 276 template = template.replace(_JDK_PATH_TOKEN, self._JDK_PATH) 277 with open(jdk_table_path, 'w') as jdk_table_fd: 278 jdk_table_fd.write(template) 279 except IOError as err: 280 logging.warning(err) 281 282 def _get_preferred_version(self): 283 """Get users' preferred IntelliJ version. 284 285 Locates the IntelliJ IDEA launch script path by following rule. 286 287 1. If config file recorded user's preference version, load it. 288 2. If config file didn't record, search them form default path if there 289 are more than one version, ask user and record it. 290 291 Returns: 292 The sh full path, or None if no IntelliJ version is installed. 293 """ 294 cefiles = _get_intellij_version_path(self._ls_ce_path) 295 uefiles = _get_intellij_version_path(self._ls_ue_path) 296 all_versions = self._get_all_versions(cefiles, uefiles) 297 if len(all_versions) > 1: 298 with AidegenConfig() as aconf: 299 if not self._config_reset and ( 300 aconf.preferred_version in all_versions): 301 return aconf.preferred_version 302 preferred = _ask_preference(all_versions) 303 if preferred: 304 aconf.preferred_version = preferred 305 return preferred 306 elif all_versions: 307 return all_versions[0] 308 return None 309 310 def _get_script_from_system(self): 311 """Get correct IntelliJ installed path from internal path. 312 313 Returns: 314 The sh full path, or None if no IntelliJ version is installed. 315 """ 316 found = self._get_preferred_version() 317 if found: 318 logging.debug('IDE internal installed path: %s.', found) 319 return found 320 321 @staticmethod 322 def _get_all_versions(cefiles, uefiles): 323 """Get all versions of launch script files. 324 325 Args: 326 cefiles: CE version launch script paths. 327 uefiles: UE version launch script paths. 328 329 Returns: 330 A list contains all versions of launch script files. 331 """ 332 all_versions = [] 333 if cefiles: 334 all_versions.extend(cefiles) 335 if uefiles: 336 all_versions.extend(uefiles) 337 return all_versions 338 339 @staticmethod 340 def _get_code_style_config(): 341 """Get Android build-in IntelliJ code style config file. 342 343 Returns: 344 None if the file is not found, otherwise a full path string of 345 Intellij Android code style file. 346 """ 347 _config_source = os.path.join(constant.ANDROID_ROOT_PATH, 'development', 348 'ide', 'intellij', 'codestyles', 349 'AndroidStyle.xml') 350 351 return _config_source if os.path.isfile(_config_source) else None 352 353 354class IdeLinuxIntelliJ(IdeIntelliJ): 355 """Provide the IDEA behavior implementation for OS Linux. 356 357 For example: 358 1. Check if IntelliJ is installed. 359 2. Launch an IntelliJ. 360 3. Config IntelliJ. 361 """ 362 363 _JDK_PATH = os.path.join(constant.ANDROID_ROOT_PATH, 364 'prebuilts/jdk/jdk8/linux-x86') 365 # TODO(b/127899277): Preserve a config for jdk version option case. 366 _IDE_JDK_TABLE_PATH = 'config/options/jdk.table.xml' 367 _JDK_PART_TEMPLATE_PATH = os.path.join( 368 constant.AIDEGEN_ROOT_PATH, 'templates/jdkTable/part.jdk.table.xml') 369 _JDK_FULL_TEMPLATE_PATH = os.path.join(constant.AIDEGEN_ROOT_PATH, 370 'templates/jdkTable/jdk.table.xml') 371 372 def __init__(self, installed_path=None, config_reset=False): 373 super().__init__(installed_path, config_reset) 374 self._bin_file_name = 'idea.sh' 375 self._bin_folders = ['/opt/intellij-*/bin'] 376 self._ls_ce_path = os.path.join('/opt/intellij-ce-2*/bin', 377 self._bin_file_name) 378 self._ls_ue_path = os.path.join('/opt/intellij-ue-2*/bin', 379 self._bin_file_name) 380 self._init_installed_path(installed_path) 381 382 def _get_config_root_paths(self): 383 """To collect the global config folder paths of IDEA as a string list. 384 385 The config folder of IntelliJ IDEA is under the user's home directory, 386 .IdeaIC20xx.x and .IntelliJIdea20xx.x are folder names for different 387 versions. 388 389 Returns: 390 A string list for IDE config root paths, and return None for failed 391 to found case. 392 """ 393 if not self._installed_path: 394 return None 395 396 _config_folders = [] 397 _config_folder = '' 398 # TODO(b/123459239): For the case that the user provides the IDEA 399 # binary path, we now collect all possible IDEA config root paths. 400 if 'e-20' not in self._installed_path: 401 _config_folders = glob.glob( 402 os.path.join(os.getenv('HOME'), '.IdeaI?20*')) 403 _config_folders.extend( 404 glob.glob(os.path.join(os.getenv('HOME'), '.IntelliJIdea20*'))) 405 logging.info('The config path list: %s.\n', _config_folders) 406 else: 407 _path_data = self._installed_path.split('-') 408 _ide_version = _path_data[2].split(os.sep)[0] 409 if _path_data[1] == 'ce': 410 _config_folder = ''.join(['.IdeaIC', _ide_version]) 411 else: 412 _config_folder = ''.join(['.IntelliJIdea', _ide_version]) 413 414 _config_folders.append( 415 os.path.join(os.getenv('HOME'), _config_folder)) 416 return _config_folders 417 418 def _get_config_folder_name(self): 419 """A interface used to provide the config sub folder name. 420 421 Returns: 422 A sub path string of the config folder. 423 """ 424 return os.path.join('config', 'codestyles') 425 426 427class IdeMacIntelliJ(IdeIntelliJ): 428 """Provide the IDEA behavior implementation for OS Mac. 429 430 For example: 431 1. Check if IntelliJ is installed. 432 2. Launch an IntelliJ. 433 3. Config IntelliJ. 434 """ 435 436 _JDK_PATH = os.path.join(constant.ANDROID_ROOT_PATH, 437 'prebuilts/jdk/jdk8/darwin-x86') 438 _IDE_JDK_TABLE_PATH = 'options/jdk.table.xml' 439 _JDK_PART_TEMPLATE_PATH = os.path.join( 440 constant.AIDEGEN_ROOT_PATH, 'templates/jdkTable/part.mac.jdk.table.xml') 441 _JDK_FULL_TEMPLATE_PATH = os.path.join( 442 constant.AIDEGEN_ROOT_PATH, 'templates/jdkTable/mac.jdk.table.xml') 443 444 def __init__(self, installed_path=None, config_reset=False): 445 super().__init__(installed_path, config_reset) 446 self._bin_file_name = 'idea' 447 self._bin_folders = ['/Applications/IntelliJ IDEA.app/Contents/MacOS'] 448 self._bin_paths = self._get_possible_bin_paths() 449 self._ls_ce_path = os.path.join( 450 '/Applications/IntelliJ IDEA CE.app/Contents/MacOS', 451 self._bin_file_name) 452 self._ls_ue_path = os.path.join( 453 '/Applications/IntelliJ IDEA.app/Contents/MacOS', 454 self._bin_file_name) 455 self._init_installed_path(installed_path) 456 457 def _get_config_root_paths(self): 458 """To collect the global config folder paths of IDEA as a string list. 459 460 Returns: 461 A string list for IDE config root paths, and return None for failed 462 to found case. 463 """ 464 if not self._installed_path: 465 return None 466 467 _config_folders = [] 468 if 'IntelliJ' in self._installed_path: 469 _config_folders = glob.glob( 470 os.path.join( 471 os.getenv('HOME'), 'Library/Preferences/IdeaI?20*')) 472 _config_folders.extend( 473 glob.glob( 474 os.path.join( 475 os.getenv('HOME'), 476 'Library/Preferences/IntelliJIdea20*'))) 477 return _config_folders 478 479 def _get_config_folder_name(self): 480 """A interface used to provide the config sub folder name. 481 482 Returns: 483 A sub path string of the config folder. 484 """ 485 return 'codeStyles' 486 487 488class IdeStudio(IdeBase): 489 """Class offers a set of Android Studio launching utilities. 490 491 For example: 492 1. Check if Android Studio is installed. 493 2. Launch an Android Studio. 494 """ 495 496 def __init__(self, installed_path=None, config_reset=False): 497 super().__init__(installed_path, config_reset) 498 self._ide_name = constant.IDE_ANDROID_STUDIO 499 500 501class IdeLinuxStudio(IdeStudio): 502 """Class offers a set of Android Studio launching utilities for OS Linux. 503 504 For example: 505 1. Check if Android Studio is installed. 506 2. Launch an Android Studio. 507 """ 508 509 def __init__(self, installed_path=None, config_reset=False): 510 super().__init__(installed_path, config_reset) 511 self._bin_file_name = 'studio.sh' 512 self._bin_folders = ['/opt/android-*/bin'] 513 self._bin_paths = self._get_possible_bin_paths() 514 self._init_installed_path(installed_path) 515 516 517class IdeMacStudio(IdeStudio): 518 """Class offers a set of Android Studio launching utilities for OS Mac. 519 520 For example: 521 1. Check if Android Studio is installed. 522 2. Launch an Android Studio. 523 """ 524 525 def __init__(self, installed_path=None, config_reset=False): 526 super().__init__(installed_path, config_reset) 527 self._bin_file_name = 'studio' 528 self._bin_folders = ['/Applications/Android Studio.app/Contents/MacOS'] 529 self._bin_paths = self._get_possible_bin_paths() 530 self._init_installed_path(installed_path) 531 532 533class IdeEclipse(IdeBase): 534 """Class offers a set of Eclipse launching utilities. 535 536 For example: 537 1. Check if Eclipse is installed. 538 2. Launch an Eclipse. 539 """ 540 541 def __init__(self, installed_path=None, config_reset=False): 542 super().__init__(installed_path, config_reset) 543 self._ide_name = constant.IDE_ECLIPSE 544 self._bin_file_name = 'eclipse*' 545 546 def _get_script_from_system(self): 547 """Get correct IDE installed path from internal path. 548 549 Remove any file with extension, the filename should be like, 'eclipse', 550 'eclipse47' and so on, check if the file is executable and filter out 551 file such as 'eclipse.ini'. 552 553 Returns: 554 The sh full path, or None if no IntelliJ version is installed. 555 """ 556 for ide_path in self._bin_paths: 557 ls_output = glob.glob(ide_path, recursive=True) 558 if ls_output: 559 ls_output = sorted(ls_output) 560 match_eclipses = [] 561 for path in ls_output: 562 if os.access(path, os.X_OK): 563 match_eclipses.append(path) 564 if match_eclipses: 565 match_eclipses = sorted(match_eclipses) 566 logging.debug('Result for checking %s after sort: %s.', 567 self._ide_name, match_eclipses[0]) 568 return match_eclipses[0] 569 logging.error('No %s installed.', self._ide_name) 570 return None 571 572 573class IdeLinuxEclipse(IdeEclipse): 574 """Class offers a set of Eclipse launching utilities for OS Linux. 575 576 For example: 577 1. Check if Eclipse is installed. 578 2. Launch an Eclipse. 579 """ 580 581 def __init__(self, installed_path=None, config_reset=False): 582 super().__init__(installed_path, config_reset) 583 self._bin_folders = ['/opt/eclipse*', '/usr/bin/'] 584 self._bin_paths = self._get_possible_bin_paths() 585 self._init_installed_path(installed_path) 586 587 588class IdeMacEclipse(IdeEclipse): 589 """Class offers a set of Eclipse launching utilities for OS Mac. 590 591 For example: 592 1. Check if Eclipse is installed. 593 2. Launch an Eclipse. 594 """ 595 596 def __init__(self, installed_path=None, config_reset=False): 597 super().__init__(installed_path, config_reset) 598 self._bin_file_name = 'Eclipse.app' 599 self._bin_folders = [os.path.expanduser('~/eclipse/**')] 600 self._bin_paths = self._get_possible_bin_paths() 601 self._init_installed_path(installed_path) 602 603 def _get_ide_cmd(self, project_file): 604 """Compose launch IDE command to run a new process and redirect output. 605 606 Args: 607 project_file: The full path of the IDE's project file. 608 609 Returns: 610 A string of launch IDE command. 611 """ 612 return ' '.join([ 613 _NOHUP, 614 'open', 615 self._installed_path.replace(' ', r'\ '), 616 os.path.dirname(project_file), _IGNORE_STD_OUT_ERR_CMD, '&' 617 ]) 618 619 620def _get_script_from_internal_path(ide_paths, ide_name): 621 """Get the studio.sh script path from internal path. 622 623 Args: 624 ide_paths: A list of IDE installed paths to be checked. 625 ide_name: The IDE name. 626 627 Returns: 628 The IDE full path or None if no Android Studio or Eclipse is installed. 629 """ 630 for ide_path in ide_paths: 631 ls_output = glob.glob(ide_path, recursive=True) 632 ls_output = sorted(ls_output) 633 if ls_output: 634 logging.debug('Result for checking %s after sort: %s.', ide_name, 635 ls_output[0]) 636 return ls_output[0] 637 logging.error('No %s installed.', ide_name) 638 return None 639 640 641def _run_ide_sh(run_sh_cmd, project_path): 642 """Run IDE launching script with an IntelliJ project path as argument. 643 644 Args: 645 run_sh_cmd: The command to launch IDE. 646 project_path: The path of IntelliJ IDEA project content. 647 """ 648 assert run_sh_cmd, 'No suitable IDE installed.' 649 logging.debug('Run command: "%s" to launch project.', run_sh_cmd) 650 try: 651 subprocess.check_call(run_sh_cmd, shell=True) 652 except subprocess.CalledProcessError as err: 653 logging.error('Launch project path %s failed with error: %s.', 654 project_path, err) 655 656 657def _walk_tree_find_ide_exe_file(top, ide_script_name): 658 """Recursively descend the directory tree rooted at top and filter out the 659 IDE executable script we need. 660 661 Args: 662 top: the tree root to be checked. 663 ide_script_name: IDE file name such i.e. IdeIntelliJ._INTELLIJ_EXE_FILE. 664 665 Returns: 666 the IDE executable script file(s) found. 667 """ 668 logging.info('Searching IDE script %s in path: %s.', ide_script_name, top) 669 for root, _, files in os.walk(top): 670 logging.debug('Search all files under %s to get %s, %s.', top, root, 671 files) 672 for file_ in fnmatch.filter(files, ide_script_name): 673 logging.debug('Use file name filter to find %s in path %s.', file_, 674 os.path.join(root, file_)) 675 yield os.path.join(root, file_) 676 677 678def _get_run_ide_cmd(sh_path, project_file): 679 """Get the command to launch IDE. 680 681 Args: 682 sh_path: The idea.sh path where IDE is installed. 683 project_file: The path of IntelliJ IDEA project file. 684 685 Returns: 686 A string: The IDE launching command. 687 """ 688 # In command usage, the space ' ' should be '\ ' for correctness. 689 return ' '.join([ 690 _NOHUP, 691 sh_path.replace(' ', r'\ '), 692 project_file, 693 _IGNORE_STD_OUT_ERR_CMD, 694 '&' 695 ]) 696 697 698def _get_script_from_file_path(input_path, ide_file_name): 699 """Get IDE executable script file from input file path. 700 701 Args: 702 input_path: the file path to be checked. 703 ide_file_name: the IDE executable script file name. 704 705 Returns: 706 An IDE executable script path if exists otherwise None. 707 """ 708 if os.path.basename(input_path).startswith(ide_file_name): 709 files_found = glob.glob(input_path) 710 if files_found: 711 return sorted(files_found)[0] 712 return None 713 714 715def _get_script_from_dir_path(input_path, ide_file_name): 716 """Get an IDE executable script file from input directory path. 717 718 Args: 719 input_path: the directory to be searched. 720 ide_file_name: the IDE executable script file name. 721 722 Returns: 723 An IDE executable script path if exists otherwise None. 724 """ 725 logging.debug('Call _get_script_from_dir_path with %s, and %s', input_path, 726 ide_file_name) 727 files_found = list(_walk_tree_find_ide_exe_file(input_path, ide_file_name)) 728 if files_found: 729 return sorted(files_found)[0] 730 return None 731 732 733def _launch_ide(project_path, run_ide_cmd, ide_name): 734 """Launches relative IDE by opening the passed project file. 735 736 Args: 737 project_path: The full path of the IDE project content. 738 run_ide_cmd: The command to launch IDE. 739 ide_name: the IDE name is to be launched. 740 """ 741 assert project_path, 'Empty content path is not allowed.' 742 logging.info('Launch %s for project content path: %s.', ide_name, 743 project_path) 744 _run_ide_sh(run_ide_cmd, project_path) 745 746 747def _is_intellij_project(project_path): 748 """Checks if the path passed in is an IntelliJ project content. 749 750 Args: 751 project_path: The full path of IDEA project content, which contains 752 .idea folder and .iml file(s). 753 754 Returns: 755 True if project_path is an IntelliJ project, False otherwise. 756 """ 757 if not os.path.isfile(project_path): 758 return os.path.isdir(project_path) and os.path.isdir( 759 os.path.join(project_path, _IDEA_FOLDER)) 760 761 _, ext = os.path.splitext(os.path.basename(project_path)) 762 if ext and _IML_EXTENSION == ext.lower(): 763 path = os.path.dirname(project_path) 764 logging.debug('Extracted path is: %s.', path) 765 return os.path.isdir(os.path.join(path, _IDEA_FOLDER)) 766 return False 767 768 769def _get_script_from_input_path(input_path, ide_file_name): 770 """Get correct IntelliJ executable script path from input path. 771 772 1. If input_path is a file, check if it is an IDE executable script file. 773 2. It input_path is a directory, search if it contains IDE executable script 774 file(s). 775 776 Args: 777 input_path: input path to be checked if it's an IDE executable 778 script. 779 ide_file_name: the IDE executable script file name. 780 781 Returns: 782 IDE executable file(s) if exists otherwise None. 783 """ 784 if not input_path: 785 return None 786 ide_path = '' 787 if os.path.isfile(input_path): 788 ide_path = _get_script_from_file_path(input_path, ide_file_name) 789 if os.path.isdir(input_path): 790 ide_path = _get_script_from_dir_path(input_path, ide_file_name) 791 if ide_path: 792 logging.debug('IDE installed path from user input: %s.', ide_path) 793 return ide_path 794 return None 795 796 797def _get_intellij_version_path(version_path): 798 """Locates the IntelliJ IDEA launch script path by version. 799 800 Args: 801 version_path: IntelliJ CE or UE version launch script path. 802 803 Returns: 804 The sh full path, or None if no such IntelliJ version is installed. 805 """ 806 ls_output = glob.glob(version_path, recursive=True) 807 if not ls_output: 808 return None 809 ls_output = sorted(ls_output, reverse=True) 810 logging.debug('Result for checking IntelliJ path %s after sorting:%s.', 811 version_path, ls_output) 812 return ls_output 813 814 815def _ask_preference(all_versions): 816 """Ask users which version they prefer. 817 818 Args: 819 all_versions: A list of all CE and UE version launch script paths. 820 821 Returns: 822 An users selected version. 823 """ 824 options = [] 825 for i, sfile in enumerate(all_versions, 1): 826 options.append('\t{}. {}'.format(i, sfile)) 827 query = ('You installed {} versions of IntelliJ:\n{}\nPlease select ' 828 'one.\t').format(len(all_versions), '\n'.join(options)) 829 return _select_intellij_version(query, all_versions) 830 831 832def _select_intellij_version(query, all_versions): 833 """Select one from different IntelliJ versions users installed. 834 835 Args: 836 query: The query message. 837 all_versions: A list of all CE and UE version launch script paths. 838 """ 839 all_numbers = [] 840 for i in range(len(all_versions)): 841 all_numbers.append(str(i + 1)) 842 input_data = input(query) 843 while not input_data in all_numbers: 844 input_data = input('Please select a number:\t') 845 return all_versions[int(input_data) - 1] 846 847 848def _get_ide(installed_path=None, ide='j', config_reset=False, is_mac=False): 849 """Get IDE to be launched according to the ide input and OS type. 850 851 Args: 852 installed_path: The IDE installed path to be checked. 853 ide: A key character of IDE to be launched. Default ide='j' is to 854 launch IntelliJ. 855 config_reset: A boolean, if true reset configuration data. 856 857 Returns: 858 A corresponding IDE instance. 859 """ 860 if is_mac: 861 return _get_mac_ide(installed_path, ide, config_reset) 862 return _get_linux_ide(installed_path, ide, config_reset) 863 864 865def _get_mac_ide(installed_path=None, ide='j', config_reset=False): 866 """Get IDE to be launched according to the ide input for OS Mac. 867 868 Args: 869 installed_path: The IDE installed path to be checked. 870 ide: A key character of IDE to be launched. Default ide='j' is to 871 launch IntelliJ. 872 config_reset: A boolean, if true reset configuration data. 873 874 Returns: 875 A corresponding IDE instance. 876 """ 877 if ide == 'e': 878 return IdeMacEclipse(installed_path) 879 if ide == 's': 880 return IdeMacStudio(installed_path) 881 return IdeMacIntelliJ(installed_path, config_reset) 882 883 884def _get_linux_ide(installed_path=None, ide='j', config_reset=False): 885 """Get IDE to be launched according to the ide input for OS Linux. 886 887 Args: 888 installed_path: The IDE installed path to be checked. 889 ide: A key character of IDE to be launched. Default ide='j' is to 890 launch IntelliJ. 891 config_reset: A boolean, if true reset configuration data. 892 893 Returns: 894 A corresponding IDE instance. 895 """ 896 if ide == 'e': 897 return IdeLinuxEclipse(installed_path) 898 if ide == 's': 899 return IdeLinuxStudio(installed_path) 900 return IdeLinuxIntelliJ(installed_path, config_reset) 901