blob: 4569c9b5273749e8995d3e35c8bdbc75693c7b17 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright 2018 - The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""It is an AIDEGen sub task : IDE operation task!
Takes a project file path as input, after passing the needed check(file
existence, IDE type, etc.), launch the project in related IDE.
Typical usage example:
ide_util_obj = IdeUtil()
if ide_util_obj.is_ide_installed():
ide_util_obj.config_ide(project_file)
ide_util_obj.launch_ide()
# Get the configuration folders of IntelliJ or Android Studio.
ide_util_obj.get_ide_config_folders()
"""
import glob
import logging
import os
import platform
import re
import subprocess
from xml.etree import ElementTree
from aidegen import constant
from aidegen import templates
from aidegen.lib import aidegen_metrics
from aidegen.lib import android_dev_os
from aidegen.lib import common_util
from aidegen.lib import config
from aidegen.lib import errors
from aidegen.lib import ide_common_util
from aidegen.lib import project_config
from aidegen.lib import project_file_gen
from aidegen.sdk import jdk_table
from aidegen.lib import xml_util
# Add 'nohup' to prevent IDE from being terminated when console is terminated.
_IDEA_FOLDER = '.idea'
_IML_EXTENSION = '.iml'
_JDK_PATH_TOKEN = '@JDKpath'
_COMPONENT_END_TAG = ' </component>'
_ECLIPSE_WS = '~/Documents/AIDEGen_Eclipse_workspace'
_ALERT_CREATE_WS = ('AIDEGen will create a workspace at %s for Eclipse, '
'Enter `y` to allow AIDEgen to automatically create the '
'workspace for you. Otherwise, you need to select the '
'workspace after Eclipse is launched.\nWould you like '
'AIDEgen to automatically create the workspace for you?'
'(y/n)' % constant.ECLIPSE_WS)
_NO_LAUNCH_IDE_CMD = """
Can not find IDE: {}, in path: {}, you can:
- add IDE executable to your $PATH
or - specify the exact IDE executable path by "aidegen -p"
or - specify "aidegen -n" to generate project file only
"""
_INFO_IMPORT_CONFIG = ('{} needs to import the application configuration for '
'the new version!\nAfter the import is finished, rerun '
'the command if your project did not launch. Please '
'follow the showing dialog to finish the import action.'
'\n\n')
CONFIG_DIR = 'config'
LINUX_JDK_PATH = os.path.join(common_util.get_android_root_dir(),
'prebuilts/jdk/jdk8/linux-x86')
LINUX_JDK_TABLE_PATH = 'config/options/jdk.table.xml'
LINUX_FILE_TYPE_PATH = 'config/options/filetypes.xml'
LINUX_ANDROID_SDK_PATH = os.path.join(os.getenv('HOME'), 'Android/Sdk')
MAC_JDK_PATH = os.path.join(common_util.get_android_root_dir(),
'prebuilts/jdk/jdk8/darwin-x86')
MAC_JDK_TABLE_PATH = 'options/jdk.table.xml'
MAC_FILE_TYPE_XML_PATH = 'options/filetypes.xml'
MAC_ANDROID_SDK_PATH = os.path.join(os.getenv('HOME'), 'Library/Android/sdk')
PATTERN_KEY = 'pattern'
TYPE_KEY = 'type'
JSON_TYPE = 'JSON'
TEST_MAPPING_NAME = 'TEST_MAPPING'
_TEST_MAPPING_TYPE = '<mapping pattern="TEST_MAPPING" type="JSON" />'
_XPATH_EXTENSION_MAP = 'component/extensionMap'
_XPATH_MAPPING = _XPATH_EXTENSION_MAP + '/mapping'
# pylint: disable=too-many-lines
class IdeUtil:
"""Provide a set of IDE operations, e.g., launch and configuration.
Attributes:
_ide: IdeBase derived instance, the related IDE object.
For example:
1. Check if IDE is installed.
2. Config IDE, e.g. config code style, SDK path, and etc.
3. Launch an IDE.
"""
def __init__(self,
installed_path=None,
ide='j',
config_reset=False,
is_mac=False):
logging.debug('IdeUtil with OS name: %s%s', platform.system(),
'(Mac)' if is_mac else '')
self._ide = _get_ide(installed_path, ide, config_reset, is_mac)
def is_ide_installed(self):
"""Checks if the IDE is already installed.
Returns:
True if IDE is installed already, otherwise False.
"""
return self._ide.is_ide_installed()
def launch_ide(self):
"""Launches the relative IDE by opening the passed project file."""
return self._ide.launch_ide()
def config_ide(self, project_abspath):
"""To config the IDE, e.g., setup code style, init SDK, and etc.
Args:
project_abspath: An absolute path of the project.
"""
self._ide.project_abspath = project_abspath
if self.is_ide_installed() and self._ide:
self._ide.apply_optional_config()
def get_default_path(self):
"""Gets IDE default installed path."""
return self._ide.default_installed_path
def ide_name(self):
"""Gets IDE name."""
return self._ide.ide_name
def get_ide_config_folders(self):
"""Gets the config folders of IDE."""
return self._ide.config_folders
class IdeBase:
"""The most base class of IDE, provides interface and partial path init.
Class Attributes:
_JDK_PATH: The path of JDK in android project.
_IDE_JDK_TABLE_PATH: The path of JDK table which record JDK info in IDE.
_IDE_FILE_TYPE_PATH: The path of filetypes.xml.
_JDK_CONTENT: A string, the content of the JDK configuration.
_DEFAULT_ANDROID_SDK_PATH: A string, the path of Android SDK.
_CONFIG_DIR: A string of the config folder name.
_SYMBOLIC_VERSIONS: A string list of the symbolic link paths of the
relevant IDE.
Attributes:
_installed_path: String for the IDE binary path.
_config_reset: Boolean, True for reset configuration, else not reset.
_bin_file_name: String for IDE executable file name.
_bin_paths: A list of all possible IDE executable file absolute paths.
_ide_name: String for IDE name.
_bin_folders: A list of all possible IDE installed paths.
config_folders: A list of all possible paths for the IntelliJ
configuration folder.
project_abspath: The absolute path of the project.
For example:
1. Check if IDE is installed.
2. Launch IDE.
3. Config IDE.
"""
_JDK_PATH = ''
_IDE_JDK_TABLE_PATH = ''
_IDE_FILE_TYPE_PATH = ''
_JDK_CONTENT = ''
_DEFAULT_ANDROID_SDK_PATH = ''
_CONFIG_DIR = ''
_SYMBOLIC_VERSIONS = []
def __init__(self, installed_path=None, config_reset=False):
self._installed_path = installed_path
self._config_reset = config_reset
self._ide_name = ''
self._bin_file_name = ''
self._bin_paths = []
self._bin_folders = []
self.config_folders = []
self.project_abspath = ''
def is_ide_installed(self):
"""Checks if IDE is already installed.
Returns:
True if IDE is installed already, otherwise False.
"""
return bool(self._installed_path)
def launch_ide(self):
"""Launches IDE by opening the passed project file."""
ide_common_util.launch_ide(self.project_abspath, self._get_ide_cmd(),
self._ide_name)
def apply_optional_config(self):
"""Do IDEA global config action.
Run code style config, SDK config.
"""
if not self._installed_path:
return
# Skip config action if there's no config folder exists.
_path_list = self._get_config_root_paths()
if not _path_list:
return
self.config_folders = _path_list.copy()
for _config_path in _path_list:
jdk_file = os.path.join(_config_path, self._IDE_JDK_TABLE_PATH)
jdk_xml = jdk_table.JDKTableXML(jdk_file, self._JDK_CONTENT,
self._JDK_PATH,
self._DEFAULT_ANDROID_SDK_PATH)
if jdk_xml.config_jdk_table_xml():
project_file_gen.gen_enable_debugger_module(
self.project_abspath, jdk_xml.android_sdk_version)
# Set the max file size in the idea.properties.
intellij_config_dir = os.path.join(_config_path, self._CONFIG_DIR)
config.IdeaProperties(intellij_config_dir).set_max_file_size()
self._add_test_mapping_file_type(_config_path)
def _add_test_mapping_file_type(self, _config_path):
"""Adds TEST_MAPPING file type.
IntelliJ can't recognize TEST_MAPPING files as the json file. It needs
adding file type mapping in filetypes.xml to recognize TEST_MAPPING
files.
Args:
_config_path: the path of IDE config.
"""
file_type_path = os.path.join(_config_path, self._IDE_FILE_TYPE_PATH)
if not os.path.isfile(file_type_path):
logging.warning('Filetypes.xml is not found.')
return
file_type_xml = xml_util.parse_xml(file_type_path)
if not file_type_xml:
logging.warning('Can\'t parse filetypes.xml.')
return
root = file_type_xml.getroot()
add_pattern = True
for mapping in root.findall(_XPATH_MAPPING):
attrib = mapping.attrib
if PATTERN_KEY in attrib and TYPE_KEY in attrib:
if attrib[PATTERN_KEY] == TEST_MAPPING_NAME:
if attrib[TYPE_KEY] != JSON_TYPE:
attrib[TYPE_KEY] = JSON_TYPE
file_type_xml.write(file_type_path)
add_pattern = False
break
if add_pattern:
root.find(_XPATH_EXTENSION_MAP).append(
ElementTree.fromstring(_TEST_MAPPING_TYPE))
pretty_xml = common_util.to_pretty_xml(root)
common_util.file_generate(file_type_path, pretty_xml)
def _get_config_root_paths(self):
"""Get the config root paths from derived class.
Returns:
A string list of IDE config paths, return multiple paths if more
than one path are found, return an empty list when none is found.
"""
raise NotImplementedError()
@property
def default_installed_path(self):
"""Gets IDE default installed path."""
return ' '.join(self._bin_folders)
@property
def ide_name(self):
"""Gets IDE name."""
return self._ide_name
def _get_ide_cmd(self):
"""Compose launch IDE command to run a new process and redirect output.
Returns:
A string of launch IDE command.
"""
return ide_common_util.get_run_ide_cmd(self._installed_path,
self.project_abspath)
def _init_installed_path(self, installed_path):
"""Initialize IDE installed path.
Args:
installed_path: the installed path to be checked.
"""
if installed_path:
path_list = ide_common_util.get_script_from_input_path(
installed_path, self._bin_file_name)
self._installed_path = path_list[0] if path_list else None
else:
self._installed_path = self._get_script_from_system()
if not self._installed_path:
logging.error('No %s installed.', self._ide_name)
return
self._set_installed_path()
def _get_script_from_system(self):
"""Get one IDE installed path from internal path.
Returns:
The sh full path, or None if not found.
"""
sh_list = self._get_existent_scripts_in_system()
return sh_list[0] if sh_list else None
def _get_possible_bin_paths(self):
"""Gets all possible IDE installed paths."""
return [os.path.join(f, self._bin_file_name) for f in self._bin_folders]
def _get_ide_from_environment_paths(self):
"""Get IDE executable binary file from environment paths.
Returns:
A string of IDE executable binary path if found, otherwise return
None.
"""
env_paths = os.environ['PATH'].split(':')
for env_path in env_paths:
path = ide_common_util.get_scripts_from_dir_path(
env_path, self._bin_file_name)
if path:
return path
return None
def _setup_ide(self):
"""The callback used to run the necessary setup work of the IDE.
When ide_util.config_ide is called to set up the JDK, SDK and some
features, the main thread will callback the Idexxx._setup_ide
to provide the chance for running the necessary setup of the specific
IDE. Default is to do nothing.
"""
def _get_existent_scripts_in_system(self):
"""Gets the relevant IDE run script path from system.
First get correct IDE installed path from internal paths, if not found
search it from environment paths.
Returns:
The list of script full path, or None if no found.
"""
return (ide_common_util.get_script_from_internal_path(self._bin_paths,
self._ide_name) or
self._get_ide_from_environment_paths())
def _get_user_preference(self, versions):
"""Make sure the version is valid and update preference if needed.
Args:
versions: A list of the IDE script path, contains the symbolic path.
Returns: An IDE script path, or None is not found.
"""
if not versions:
return None
if len(versions) == 1:
return versions[0]
with config.AidegenConfig() as conf:
if not self._config_reset and (conf.preferred_version(self.ide_name)
in versions):
return conf.preferred_version(self.ide_name)
display_versions = self._merge_symbolic_version(versions)
preferred = ide_common_util.ask_preference(display_versions,
self.ide_name)
if preferred:
conf.set_preferred_version(self._get_real_path(preferred),
self.ide_name)
return conf.preferred_version(self.ide_name)
def _set_installed_path(self):
"""Write the user's input installed path into the config file.
If users input an existent IDE installed path, we should keep it in
the configuration.
"""
if self._installed_path:
with config.AidegenConfig() as aconf:
aconf.set_preferred_version(self._installed_path, self.ide_name)
def _merge_symbolic_version(self, versions):
"""Merge the duplicate version of symbolic links.
Stable and beta versions are a symbolic link to an existing version.
This function assemble symbolic and real to make it more clear to read.
Ex:
['/opt/intellij-ce-stable/bin/idea.sh',
'/opt/intellij-ce-2019.1/bin/idea.sh'] to
['/opt/intellij-ce-stable/bin/idea.sh ->
/opt/intellij-ce-2019.1/bin/idea.sh',
'/opt/intellij-ce-2019.1/bin/idea.sh']
Args:
versions: A list of all installed versions.
Returns:
A list of versions to show for user to select. It may contain
'symbolic_path/idea.sh -> original_path/idea.sh'.
"""
display_versions = versions[:]
for symbolic in self._SYMBOLIC_VERSIONS:
if symbolic in display_versions and (os.path.isfile(symbolic)):
real_path = os.path.realpath(symbolic)
for index, path in enumerate(display_versions):
if path == symbolic:
display_versions[index] = ' -> '.join(
[display_versions[index], real_path])
break
return display_versions
@staticmethod
def _get_real_path(path):
""" Get real path from merged path.
Turn the path string "/opt/intellij-ce-stable/bin/idea.sh -> /opt/
intellij-ce-2019.2/bin/idea.sh" into
"/opt/intellij-ce-stable/bin/idea.sh"
Args:
path: A path string may be merged with symbolic path.
Returns:
The real IntelliJ installed path.
"""
return path.split()[0]
class IdeIntelliJ(IdeBase):
"""Provide basic IntelliJ ops, e.g., launch IDEA, and config IntelliJ.
For example:
1. Check if IntelliJ is installed.
2. Launch an IntelliJ.
3. Config IntelliJ.
"""
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
self._ide_name = constant.IDE_INTELLIJ
self._ls_ce_path = ''
self._ls_ue_path = ''
def _get_config_root_paths(self):
"""Get the config root paths from derived class.
Returns:
A string list of IDE config paths, return multiple paths if more
than one path are found, return an empty list when none is found.
"""
raise NotImplementedError()
def _get_preferred_version(self):
"""Get the user's preferred IntelliJ version.
Locates the IntelliJ IDEA launch script path by following rule.
1. If config file recorded user's preference version, load it.
2. If config file didn't record, search them form default path if there
are more than one version, ask user and record it.
Returns:
The sh full path, or None if no IntelliJ version is installed.
"""
ce_paths = ide_common_util.get_intellij_version_path(self._ls_ce_path)
ue_paths = ide_common_util.get_intellij_version_path(self._ls_ue_path)
all_versions = self._get_all_versions(ce_paths, ue_paths)
tmp_versions = all_versions.copy()
for version in tmp_versions:
real_version = os.path.realpath(version)
if config.AidegenConfig.deprecated_intellij_version(real_version):
all_versions.remove(version)
return self._get_user_preference(all_versions)
def _setup_ide(self):
"""The callback used to run the necessary setup work for the IDE.
IntelliJ has a default flow to let the user import the configuration
from the previous version, aidegen makes sure not to break the behavior
by checking in this callback implementation.
"""
run_script_path = os.path.realpath(self._installed_path)
app_folder = self._get_application_path(run_script_path)
if not app_folder:
logging.warning('\nInvalid IDE installed path.')
return
show_hint = False
folder_path = os.path.join(os.getenv('HOME'), app_folder,
'config', 'plugins')
import_process = None
while not os.path.isdir(folder_path):
# Guide the user to go through the IDE flow.
if not show_hint:
print('\n{} {}'.format(common_util.COLORED_INFO('INFO:'),
_INFO_IMPORT_CONFIG.format(
self.ide_name)))
try:
import_process = subprocess.Popen(
ide_common_util.get_run_ide_cmd(run_script_path, '',
False), shell=True)
except (subprocess.SubprocessError, ValueError):
logging.warning('\nSubprocess call gets the invalid input.')
finally:
show_hint = True
try:
import_process.wait(1)
except subprocess.TimeoutExpired:
import_process.terminate()
return
def _get_script_from_system(self):
"""Get correct IntelliJ installed path from internal path.
Returns:
The sh full path, or None if no IntelliJ version is installed.
"""
found = self._get_preferred_version()
if found:
logging.debug('IDE internal installed path: %s.', found)
return found
@staticmethod
def _get_all_versions(cefiles, uefiles):
"""Get all versions of launch script files.
Args:
cefiles: CE version launch script paths.
uefiles: UE version launch script paths.
Returns:
A list contains all versions of launch script files.
"""
all_versions = []
if cefiles:
all_versions.extend(cefiles)
if uefiles:
all_versions.extend(uefiles)
return all_versions
@staticmethod
def _get_application_path(run_script_path):
"""Get the relevant configuration folder based on the run script path.
Args:
run_script_path: The string of the run script path for the IntelliJ.
Returns:
The string of the IntelliJ application folder name or None if the
run_script_path is invalid. The returned folder format is as
follows,
1. .IdeaIC2019.3
2. .IntelliJIdea2019.3
"""
if not run_script_path or not os.path.isfile(run_script_path):
return None
index = str.find(run_script_path, 'intellij-')
target_path = None if index == -1 else run_script_path[index:]
if not target_path or '-' not in run_script_path:
return None
path_data = target_path.split('-')
if not path_data or len(path_data) < 3:
return None
config_folder = None
ide_version = path_data[2].split(os.sep)[0]
if path_data[1] == 'ce':
config_folder = ''.join(['.IdeaIC', ide_version])
elif path_data[1] == 'ue':
config_folder = ''.join(['.IntelliJIdea', ide_version])
return config_folder
class IdeLinuxIntelliJ(IdeIntelliJ):
"""Provide the IDEA behavior implementation for OS Linux.
Class Attributes:
_INTELLIJ_RE: Regular expression of IntelliJ installed name in GLinux.
For example:
1. Check if IntelliJ is installed.
2. Launch an IntelliJ.
3. Config IntelliJ.
"""
_JDK_PATH = LINUX_JDK_PATH
# TODO(b/127899277): Preserve a config for jdk version option case.
_CONFIG_DIR = CONFIG_DIR
_IDE_JDK_TABLE_PATH = LINUX_JDK_TABLE_PATH
_IDE_FILE_TYPE_PATH = LINUX_FILE_TYPE_PATH
_JDK_CONTENT = templates.LINUX_JDK_XML
_DEFAULT_ANDROID_SDK_PATH = LINUX_ANDROID_SDK_PATH
_SYMBOLIC_VERSIONS = ['/opt/intellij-ce-stable/bin/idea.sh',
'/opt/intellij-ue-stable/bin/idea.sh',
'/opt/intellij-ce-beta/bin/idea.sh',
'/opt/intellij-ue-beta/bin/idea.sh']
_INTELLIJ_RE = re.compile(r'intellij-(ce|ue)-')
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
self._bin_file_name = 'idea.sh'
self._bin_folders = ['/opt/intellij-*/bin']
self._ls_ce_path = os.path.join('/opt/intellij-ce-*/bin',
self._bin_file_name)
self._ls_ue_path = os.path.join('/opt/intellij-ue-*/bin',
self._bin_file_name)
self._init_installed_path(installed_path)
def _get_config_root_paths(self):
"""To collect the global config folder paths of IDEA as a string list.
The config folder of IntelliJ IDEA is under the user's home directory,
.IdeaIC20xx.x and .IntelliJIdea20xx.x are folder names for different
versions.
Returns:
A string list for IDE config root paths, and return an empty list
when none is found.
"""
if not self._installed_path:
return None
_config_folders = []
_config_folder = ''
if IdeLinuxIntelliJ._INTELLIJ_RE.search(self._installed_path):
_path_data = os.path.realpath(self._installed_path)
_config_folder = self._get_application_path(_path_data)
if not _config_folder:
return None
if not os.path.isdir(os.path.join(os.getenv('HOME'),
_config_folder)):
logging.debug("\nThe config folder: %s doesn't exist",
_config_folder)
self._setup_ide()
_config_folders.append(
os.path.join(os.getenv('HOME'), _config_folder))
else:
# TODO(b/123459239): For the case that the user provides the IDEA
# binary path, we now collect all possible IDEA config root paths.
_config_folders = glob.glob(
os.path.join(os.getenv('HOME'), '.IdeaI?20*'))
_config_folders.extend(
glob.glob(os.path.join(os.getenv('HOME'), '.IntelliJIdea20*')))
logging.debug('The config path list: %s.', _config_folders)
return _config_folders
class IdeMacIntelliJ(IdeIntelliJ):
"""Provide the IDEA behavior implementation for OS Mac.
For example:
1. Check if IntelliJ is installed.
2. Launch an IntelliJ.
3. Config IntelliJ.
"""
_JDK_PATH = MAC_JDK_PATH
_IDE_JDK_TABLE_PATH = MAC_JDK_TABLE_PATH
_IDE_FILE_TYPE_PATH = MAC_FILE_TYPE_XML_PATH
_JDK_CONTENT = templates.MAC_JDK_XML
_DEFAULT_ANDROID_SDK_PATH = MAC_ANDROID_SDK_PATH
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
self._bin_file_name = 'idea'
self._bin_folders = ['/Applications/IntelliJ IDEA.app/Contents/MacOS']
self._bin_paths = self._get_possible_bin_paths()
self._ls_ce_path = os.path.join(
'/Applications/IntelliJ IDEA CE.app/Contents/MacOS',
self._bin_file_name)
self._ls_ue_path = os.path.join(
'/Applications/IntelliJ IDEA.app/Contents/MacOS',
self._bin_file_name)
self._init_installed_path(installed_path)
def _get_config_root_paths(self):
"""To collect the global config folder paths of IDEA as a string list.
Returns:
A string list for IDE config root paths, and return an empty list
when none is found.
"""
if not self._installed_path:
return None
_config_folders = []
if 'IntelliJ' in self._installed_path:
_config_folders = glob.glob(
os.path.join(
os.getenv('HOME'), 'Library/Preferences/IdeaI?20*'))
_config_folders.extend(
glob.glob(
os.path.join(
os.getenv('HOME'),
'Library/Preferences/IntelliJIdea20*')))
return _config_folders
class IdeStudio(IdeBase):
"""Class offers a set of Android Studio launching utilities.
For example:
1. Check if Android Studio is installed.
2. Launch an Android Studio.
3. Config Android Studio.
"""
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
self._ide_name = constant.IDE_ANDROID_STUDIO
def _get_config_root_paths(self):
"""Get the config root paths from derived class.
Returns:
A string list of IDE config paths, return multiple paths if more
than one path are found, return an empty list when none is found.
"""
raise NotImplementedError()
def _get_script_from_system(self):
"""Get correct Studio installed path from internal path.
Returns:
The sh full path, or None if no Studio version is installed.
"""
found = self._get_preferred_version()
if found:
logging.debug('IDE internal installed path: %s.', found)
return found
def _get_preferred_version(self):
"""Get the user's preferred Studio version.
Locates the Studio launch script path by following rule.
1. If config file recorded user's preference version, load it.
2. If config file didn't record, search them form default path if there
are more than one version, ask user and record it.
Returns:
The sh full path, or None if no Studio version is installed.
"""
versions = self._get_existent_scripts_in_system()
if not versions:
return None
for version in versions:
real_version = os.path.realpath(version)
if config.AidegenConfig.deprecated_studio_version(real_version):
versions.remove(version)
return self._get_user_preference(versions)
def apply_optional_config(self):
"""Do the configuration of Android Studio.
Configures code style and SDK for Java project and do nothing for
others.
"""
if not self.project_abspath:
return
# TODO(b/150662865): The following workaround should be replaced.
# Since the path of the artifact for Java is the .idea directory but
# native is a CMakeLists.txt file using this to workaround first.
if os.path.isfile(self.project_abspath):
return
if os.path.isdir(self.project_abspath):
IdeBase.apply_optional_config(self)
class IdeLinuxStudio(IdeStudio):
"""Class offers a set of Android Studio launching utilities for OS Linux.
For example:
1. Check if Android Studio is installed.
2. Launch an Android Studio.
3. Config Android Studio.
"""
_JDK_PATH = LINUX_JDK_PATH
_CONFIG_DIR = CONFIG_DIR
_IDE_JDK_TABLE_PATH = LINUX_JDK_TABLE_PATH
_JDK_CONTENT = templates.LINUX_JDK_XML
_DEFAULT_ANDROID_SDK_PATH = LINUX_ANDROID_SDK_PATH
_SYMBOLIC_VERSIONS = [
'/opt/android-studio-with-blaze-stable/bin/studio.sh',
'/opt/android-studio-stable/bin/studio.sh',
'/opt/android-studio-with-blaze-beta/bin/studio.sh',
'/opt/android-studio-beta/bin/studio.sh']
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
self._bin_file_name = 'studio.sh'
self._bin_folders = ['/opt/android-studio*/bin']
self._bin_paths = self._get_possible_bin_paths()
self._init_installed_path(installed_path)
def _get_config_root_paths(self):
"""Collect the global config folder paths as a string list.
Returns:
A string list for IDE config root paths, and return an empty list
when none is found.
"""
return glob.glob(os.path.join(os.getenv('HOME'), '.AndroidStudio*'))
class IdeMacStudio(IdeStudio):
"""Class offers a set of Android Studio launching utilities for OS Mac.
For example:
1. Check if Android Studio is installed.
2. Launch an Android Studio.
3. Config Android Studio.
"""
_JDK_PATH = MAC_JDK_PATH
_IDE_JDK_TABLE_PATH = MAC_JDK_TABLE_PATH
_JDK_CONTENT = templates.MAC_JDK_XML
_DEFAULT_ANDROID_SDK_PATH = MAC_ANDROID_SDK_PATH
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
self._bin_file_name = 'studio'
self._bin_folders = ['/Applications/Android Studio.app/Contents/MacOS']
self._bin_paths = self._get_possible_bin_paths()
self._init_installed_path(installed_path)
def _get_config_root_paths(self):
"""Collect the global config folder paths as a string list.
Returns:
A string list for IDE config root paths, and return an empty list
when none is found.
"""
return glob.glob(
os.path.join(
os.getenv('HOME'), 'Library/Preferences/AndroidStudio*'))
class IdeEclipse(IdeBase):
"""Class offers a set of Eclipse launching utilities.
Attributes:
cmd: A list of the build command.
For example:
1. Check if Eclipse is installed.
2. Launch an Eclipse.
"""
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
self._ide_name = constant.IDE_ECLIPSE
self._bin_file_name = 'eclipse'
self.cmd = []
def _get_script_from_system(self):
"""Get correct IDE installed path from internal path.
Remove any file with extension, the filename should be like, 'eclipse',
'eclipse47' and so on, check if the file is executable and filter out
file such as 'eclipse.ini'.
Returns:
The sh full path, or None if no IntelliJ version is installed.
"""
for ide_path in self._bin_paths:
# The binary name of Eclipse could be eclipse47, eclipse49,
# eclipse47_testing or eclipse49_testing. So finding the matched
# binary by /path/to/ide/eclipse*.
ls_output = glob.glob(ide_path + '*', recursive=True)
if ls_output:
ls_output = sorted(ls_output)
match_eclipses = []
for path in ls_output:
if os.access(path, os.X_OK):
match_eclipses.append(path)
if match_eclipses:
match_eclipses = sorted(match_eclipses)
logging.debug('Result for checking %s after sort: %s.',
self._ide_name, match_eclipses[0])
return match_eclipses[0]
return None
def _get_ide_cmd(self):
"""Compose launch IDE command to run a new process and redirect output.
AIDEGen will create a default workspace
~/Documents/AIDEGen_Eclipse_workspace for users if they agree to do
that. Also, we could not import the default project through the command
line so remove the project path argument.
Returns:
A string of launch IDE command.
"""
if (os.path.exists(os.path.expanduser(constant.ECLIPSE_WS))
or str(input(_ALERT_CREATE_WS)).lower() == 'y'):
self.cmd.extend(['-data', constant.ECLIPSE_WS])
self.cmd.extend([constant.IGNORE_STD_OUT_ERR_CMD, '&'])
return ' '.join(self.cmd)
def apply_optional_config(self):
"""Override to do nothing."""
def _get_config_root_paths(self):
"""Override to do nothing."""
class IdeLinuxEclipse(IdeEclipse):
"""Class offers a set of Eclipse launching utilities for OS Linux.
For example:
1. Check if Eclipse is installed.
2. Launch an Eclipse.
"""
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
self._bin_folders = ['/opt/eclipse*', '/usr/bin/']
self._bin_paths = self._get_possible_bin_paths()
self._init_installed_path(installed_path)
self.cmd = [constant.NOHUP, self._installed_path.replace(' ', r'\ ')]
class IdeMacEclipse(IdeEclipse):
"""Class offers a set of Eclipse launching utilities for OS Mac.
For example:
1. Check if Eclipse is installed.
2. Launch an Eclipse.
"""
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
self._bin_file_name = 'eclipse'
self._bin_folders = [os.path.expanduser('~/eclipse/**')]
self._bin_paths = self._get_possible_bin_paths()
self._init_installed_path(installed_path)
self.cmd = [self._installed_path.replace(' ', r'\ ')]
class IdeCLion(IdeBase):
"""Class offers a set of CLion launching utilities.
For example:
1. Check if CLion is installed.
2. Launch an CLion.
"""
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
self._ide_name = constant.IDE_CLION
def apply_optional_config(self):
"""Override to do nothing."""
def _get_config_root_paths(self):
"""Override to do nothing."""
class IdeLinuxCLion(IdeCLion):
"""Class offers a set of CLion launching utilities for OS Linux.
For example:
1. Check if CLion is installed.
2. Launch an CLion.
"""
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
self._bin_file_name = 'clion.sh'
# TODO(b/141288011): Handle /opt/clion-*/bin to let users choose a
# preferred version of CLion in the future.
self._bin_folders = ['/opt/clion-stable/bin']
self._bin_paths = self._get_possible_bin_paths()
self._init_installed_path(installed_path)
class IdeMacCLion(IdeCLion):
"""Class offers a set of Android Studio launching utilities for OS Mac.
For example:
1. Check if Android Studio is installed.
2. Launch an Android Studio.
"""
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
self._bin_file_name = 'clion'
self._bin_folders = ['/Applications/CLion.app/Contents/MacOS/CLion']
self._bin_paths = self._get_possible_bin_paths()
self._init_installed_path(installed_path)
class IdeVSCode(IdeBase):
"""Class offers a set of VSCode launching utilities.
For example:
1. Check if VSCode is installed.
2. Launch an VSCode.
"""
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
self._ide_name = constant.IDE_VSCODE
def apply_optional_config(self):
"""Override to do nothing."""
def _get_config_root_paths(self):
"""Override to do nothing."""
class IdeLinuxVSCode(IdeVSCode):
"""Class offers a set of VSCode launching utilities for OS Linux."""
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
self._bin_file_name = 'code'
self._bin_folders = ['/usr/bin']
self._bin_paths = self._get_possible_bin_paths()
self._init_installed_path(installed_path)
class IdeMacVSCode(IdeVSCode):
"""Class offers a set of VSCode launching utilities for OS Mac."""
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
self._bin_file_name = 'code'
self._bin_folders = ['/usr/local/bin']
self._bin_paths = self._get_possible_bin_paths()
self._init_installed_path(installed_path)
def get_ide_util_instance(ide='j'):
"""Get an IdeUtil class instance for launching IDE.
Args:
ide: A key character of IDE to be launched. Default ide='j' is to
launch IntelliJ.
Returns:
An IdeUtil class instance.
"""
conf = project_config.ProjectConfig.get_instance()
if not conf.is_launch_ide:
return None
is_mac = (android_dev_os.AndroidDevOS.MAC == android_dev_os.AndroidDevOS.
get_os_type())
tool = IdeUtil(conf.ide_installed_path, ide, conf.config_reset, is_mac)
if not tool.is_ide_installed():
ipath = conf.ide_installed_path or tool.get_default_path()
err = _NO_LAUNCH_IDE_CMD.format(constant.IDE_NAME_DICT[ide], ipath)
logging.error(err)
stack_trace = common_util.remove_user_home_path(err)
logs = '%s exists? %s' % (common_util.remove_user_home_path(ipath),
os.path.exists(ipath))
aidegen_metrics.ends_asuite_metrics(constant.IDE_LAUNCH_FAILURE,
stack_trace,
logs)
raise errors.IDENotExistError(err)
return tool
def _get_ide(installed_path=None, ide='j', config_reset=False, is_mac=False):
"""Get IDE to be launched according to the ide input and OS type.
Args:
installed_path: The IDE installed path to be checked.
ide: A key character of IDE to be launched. Default ide='j' is to
launch IntelliJ.
config_reset: A boolean, if true reset configuration data.
Returns:
A corresponding IDE instance.
"""
if is_mac:
return _get_mac_ide(installed_path, ide, config_reset)
return _get_linux_ide(installed_path, ide, config_reset)
def _get_mac_ide(installed_path=None, ide='j', config_reset=False):
"""Get IDE to be launched according to the ide input for OS Mac.
Args:
installed_path: The IDE installed path to be checked.
ide: A key character of IDE to be launched. Default ide='j' is to
launch IntelliJ.
config_reset: A boolean, if true reset configuration data.
Returns:
A corresponding IDE instance.
"""
if ide == 'e':
return IdeMacEclipse(installed_path, config_reset)
if ide == 's':
return IdeMacStudio(installed_path, config_reset)
if ide == 'c':
return IdeMacCLion(installed_path, config_reset)
if ide == 'v':
return IdeMacVSCode(installed_path, config_reset)
return IdeMacIntelliJ(installed_path, config_reset)
def _get_linux_ide(installed_path=None, ide='j', config_reset=False):
"""Get IDE to be launched according to the ide input for OS Linux.
Args:
installed_path: The IDE installed path to be checked.
ide: A key character of IDE to be launched. Default ide='j' is to
launch IntelliJ.
config_reset: A boolean, if true reset configuration data.
Returns:
A corresponding IDE instance.
"""
if ide == 'e':
return IdeLinuxEclipse(installed_path, config_reset)
if ide == 's':
return IdeLinuxStudio(installed_path, config_reset)
if ide == 'c':
return IdeLinuxCLion(installed_path, config_reset)
if ide == 'v':
return IdeLinuxVSCode(installed_path, config_reset)
return IdeLinuxIntelliJ(installed_path, config_reset)