280 lines
11 KiB
Python
280 lines
11 KiB
Python
#!/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)
|