| #!/usr/bin/env python3 |
| # |
| # Copyright 2020 - 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. |
| |
| """Configs the jdk.table.xml. |
| |
| In order to enable the feature "Attach debugger to Android process" in Android |
| Studio or IntelliJ, AIDEGen needs the JDK and Android SDK been set-up. The class |
| JDKTableXML parses the jdk.table.xml to find the existing JDK and Android SDK |
| information. If they do not exist, AIDEGen will create them. |
| |
| Usage example: |
| jdk_table_xml = JDKTableXML(jdk_table_xml_file, |
| jdk_template, |
| default_jdk_path, |
| default_android_sdk_path) |
| if jdk_table_xml.config_jdk_table_xml(): |
| android_sdk_version = jdk_table_xml.android_sdk_version |
| """ |
| |
| from __future__ import absolute_import |
| |
| import os |
| |
| from xml.etree import ElementTree |
| |
| from aidegen import constant |
| from aidegen import templates |
| from aidegen.lib import aidegen_metrics |
| from aidegen.lib import common_util |
| from aidegen.lib import xml_util |
| from aidegen.sdk import android_sdk |
| |
| |
| class JDKTableXML(): |
| """Configs jdk.table.xml for IntelliJ and Android Studio. |
| |
| Attributes: |
| _config_file: The absolute file path of the jdk.table.xml, the file |
| might not exist. |
| _jdk_content: A string, the content of the JDK configuration. |
| _jdk_path: The path of JDK in android project. |
| _default_android_sdk_path: The default path to the Android SDK. |
| _platform_version: The version name of the platform, e.g. android-29 |
| _android_sdk_version: The version name of the Android SDK in the |
| jdk.table.xml, e.g. Android API 29 Platform |
| _modify_config: A boolean, True to write new content to jdk.table.xml. |
| _xml: An xml.etree.ElementTree object contains the XML parsing result. |
| _sdk: An AndroidSDK object to get the Android SDK path and platform |
| mapping. |
| """ |
| _JDK = 'jdk' |
| _NAME = 'name' |
| _TYPE = 'type' |
| _VALUE = 'value' |
| _SDK = 'sdk' |
| _HOMEPATH = 'homePath' |
| _ADDITIONAL = 'additional' |
| _ANDROID_SDK = 'Android SDK' |
| _JAVA_SDK = 'JavaSDK' |
| _JDK_VERSION = 'JDK18' |
| _APPLICATION = 'application' |
| _COMPONENT = 'component' |
| _PROJECTJDKTABLE = 'ProjectJdkTable' |
| _LAST_TAG_TAIL = '\n ' |
| _NEW_TAG_TAIL = '\n ' |
| _ANDROID_SDK_VERSION = 'Android API {CODE_NAME} Platform' |
| _DEFAULT_JDK_TABLE_XML = os.path.join(common_util.get_android_root_dir(), |
| constant.AIDEGEN_ROOT_PATH, |
| 'data', |
| 'jdk.table.xml') |
| _ILLEGAL_XML = ('The {XML} is not an useful XML file for IntelliJ. Do you ' |
| 'agree AIDEGen override it?(y/n)') |
| _IGNORE_XML_WARNING = ('The {XML} is not an useful XML file for IntelliJ. ' |
| 'It causes the feature "Attach debugger to Android ' |
| 'process" to be disabled.') |
| |
| def __init__(self, config_file, jdk_content, jdk_path, |
| default_android_sdk_path): |
| """JDKTableXML initialize. |
| |
| Args: |
| config_file: The absolute file path of the jdk.table.xml, the file |
| might not exist. |
| jdk_content: A string, the content of the JDK configuration. |
| jdk_path: The path of JDK in android project. |
| default_android_sdk_path: The default absolute path to the Android |
| SDK. |
| """ |
| self._config_file = config_file |
| self._jdk_content = jdk_content |
| self._jdk_path = jdk_path |
| self._default_android_sdk_path = default_android_sdk_path |
| self._xml = None |
| if os.path.exists(config_file): |
| xml_file = config_file |
| else: |
| xml_file = self._DEFAULT_JDK_TABLE_XML |
| common_util.file_generate(xml_file, templates.JDK_TABLE_XML) |
| self._xml = xml_util.parse_xml(xml_file) |
| self._platform_version = None |
| self._android_sdk_version = None |
| self._modify_config = False |
| self._sdk = android_sdk.AndroidSDK() |
| |
| @property |
| def android_sdk_version(self): |
| """Gets the Android SDK version.""" |
| return self._android_sdk_version |
| |
| def _check_structure(self): |
| """Checks the XML's structure is correct. |
| |
| The content of the XML file should have a root tag as <application> and |
| a child tag <component> of it. |
| E.g. |
| <application> |
| <component name="ProjectJdkTable"> |
| ... |
| </component> |
| </application> |
| |
| Returns: |
| Boolean: True if the structure is correct, otherwise False. |
| """ |
| if (not self._xml or self._xml.getroot().tag != self._APPLICATION |
| or self._xml.find(self._COMPONENT) is None |
| or self._xml.find(self._COMPONENT).tag != self._COMPONENT): |
| return False |
| return self._xml.find(self._COMPONENT).get( |
| self._NAME) == self._PROJECTJDKTABLE |
| |
| def _override_xml(self): |
| """Overrides the XML file when it's invalid. |
| |
| Returns: |
| A boolean, True when developers choose to override the XML file, |
| otherwise False. |
| """ |
| input_data = input(self._ILLEGAL_XML.format(XML=self._config_file)) |
| while input_data not in ('y', 'n'): |
| input_data = input('Please type y(Yes) or n(No): ') |
| if input_data == 'y': |
| # Record the exception about wrong XML format. |
| if self._xml: |
| aidegen_metrics.send_exception_metrics( |
| constant.XML_PARSING_FAILURE, '', |
| ElementTree.tostring(self._xml.getroot()), '') |
| self._xml = xml_util.parse_xml(self._DEFAULT_JDK_TABLE_XML) |
| return True |
| return False |
| |
| def _check_jdk18_in_xml(self): |
| """Checks if the JDK18 is already set in jdk.table.xml. |
| |
| Returns: |
| Boolean: True if the JDK18 exists else False. |
| """ |
| for jdk in self._xml.iter(self._JDK): |
| _name = jdk.find(self._NAME) |
| _type = jdk.find(self._TYPE) |
| if None in (_name, _type): |
| continue |
| if (_type.get(self._VALUE) == self._JAVA_SDK |
| and _name.get(self._VALUE) == self._JDK_VERSION): |
| return True |
| return False |
| |
| def _check_android_sdk_in_xml(self): |
| """Checks if the Android SDK is already set in jdk.table.xml. |
| |
| If the Android SDK exists in xml, validate the value of Android SDK path |
| and platform version. |
| 1. Check if the Android SDK path is valid. |
| 2. Check if the platform version exists in the Android SDK. |
| The Android SDK version can be used to generate enble_debugger module |
| when condition 1 and 2 are true. |
| |
| Returns: |
| Boolean: True if the Android SDK configuration exists, otherwise |
| False. |
| """ |
| for tag in self._xml.iter(self._JDK): |
| _name = tag.find(self._NAME) |
| _type = tag.find(self._TYPE) |
| _homepath = tag.find(self._HOMEPATH) |
| _additional = tag.find(self._ADDITIONAL) |
| if None in (_name, _type, _homepath, _additional): |
| continue |
| |
| tag_type = _type.get(self._VALUE) |
| home_path = _homepath.get(self._VALUE).replace( |
| constant.USER_HOME, os.path.expanduser('~')) |
| platform = _additional.get(self._SDK) |
| if (tag_type != self._ANDROID_SDK |
| or not self._sdk.is_android_sdk_path(home_path) |
| or platform not in self._sdk.platform_mapping): |
| continue |
| self._android_sdk_version = _name.get(self._VALUE) |
| self._platform_version = platform |
| return True |
| return False |
| |
| def _append_config(self, new_config): |
| """Adds a <jdk> configuration at the last of <component>. |
| |
| Args: |
| new_config: A string of new <jdk> configuration. |
| """ |
| node = ElementTree.fromstring(new_config) |
| node.tail = self._NEW_TAG_TAIL |
| component = self._xml.getroot().find(self._COMPONENT) |
| if len(component) > 0: |
| component[-1].tail = self._LAST_TAG_TAIL |
| else: |
| component.text = self._LAST_TAG_TAIL |
| self._xml.getroot().find(self._COMPONENT).append(node) |
| |
| def _generate_jdk_config_string(self): |
| """Generates the default JDK configuration.""" |
| if self._check_jdk18_in_xml(): |
| return |
| self._append_config(self._jdk_content.format(JDKpath=self._jdk_path)) |
| self._modify_config = True |
| |
| def _generate_sdk_config_string(self): |
| """Generates Android SDK configuration.""" |
| if self._check_android_sdk_in_xml(): |
| return |
| if self._sdk.path_analysis(self._default_android_sdk_path): |
| # TODO(b/151582629): Revise the API_LEVEL to CODE_NAME when |
| # abandoning the sdk_config.py. |
| self._append_config(templates.ANDROID_SDK_XML.format( |
| ANDROID_SDK_PATH=self._sdk.android_sdk_path, |
| CODE_NAME=self._sdk.max_code_name)) |
| self._android_sdk_version = self._ANDROID_SDK_VERSION.format( |
| CODE_NAME=self._sdk.max_code_name) |
| self._modify_config = True |
| return |
| # Record the exception about missing Android SDK. |
| aidegen_metrics.send_exception_metrics( |
| constant.LOCATE_SDK_PATH_FAILURE, '', |
| ElementTree.tostring(self._xml.getroot()), '') |
| |
| def config_jdk_table_xml(self): |
| """Configures the jdk.table.xml. |
| |
| 1. Generate the JDK18 configuration if it does not exist. |
| 2. Generate the Android SDK configuration if it does not exist and |
| save the Android SDK path. |
| 3. Update the jdk.table.xml if AIDEGen needs to append JDK18 or |
| Android SDK configuration. |
| |
| Returns: |
| A boolean, True when get the Android SDK version, otherwise False. |
| """ |
| if not self._check_structure() and not self._override_xml(): |
| print(self._IGNORE_XML_WARNING.format(XML=self._config_file)) |
| return False |
| self._generate_jdk_config_string() |
| self._generate_sdk_config_string() |
| if self._modify_config: |
| if not os.path.exists(self._config_file): |
| common_util.file_generate( |
| self._config_file, templates.JDK_TABLE_XML) |
| self._xml.write(self._config_file) |
| return bool(self._android_sdk_version) |