• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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