1#!/usr/bin/env python3 2# 3# Copyright 2020 - 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"""Config Android SDK information. 18 19In order to create the configuration of Android SDK in IntelliJ automatically, 20parses the Android SDK information from the Android SDK path. 21 22Usage example: 23 android_sdk = AndroidSDK() 24 android_sdk.path_analysis(default_sdk_path) 25 api_level = android_sdk.max_api_level 26 android_sdk_path = android_sdk.android_sdk_path 27 platform_mapping = android_sdk.platform_mapping 28""" 29 30from __future__ import absolute_import 31 32import glob 33import os 34import re 35 36from aidegen.lib import common_util 37 38 39class AndroidSDK: 40 """Configures API level from the Android SDK path. 41 42 Attributes: 43 _android_sdk_path: The path to the Android SDK, None if the Android SDK 44 doesn't exist. 45 _max_api_level: An integer, the max API level in the platforms folder. 46 _max_code_name: A string, the code name of the max API level. 47 _platform_mapping: A dictionary of Android platform versions mapping to 48 the API level and the Android version code name. 49 e.g. 50 { 51 'android-29': {'api_level': 29, 'code_name': '29'}, 52 'android-Q': {'api_level': 29, 'code_name': 'Q'} 53 } 54 """ 55 56 _API_LEVEL = 'api_level' 57 _CODE_NAME = 'code_name' 58 _RE_API_LEVEL = re.compile(r'AndroidVersion.ApiLevel=(?P<api_level>[\d]+)') 59 _RE_CODE_NAME = re.compile(r'AndroidVersion.CodeName=(?P<code_name>[A-Z])') 60 _GLOB_PROPERTIES_FILE = os.path.join('platforms', 'android-*', 61 'source.properties') 62 _INPUT_QUERY_TIMES = 3 63 _ENTER_ANDROID_SDK_PATH = ('\nThe Android SDK folder:{} doesn\'t exist. ' 64 'The debug function "Attach debugger to Android ' 65 'process" is disabled without Android SDK in ' 66 'IntelliJ or Android Studio. Please set it up ' 67 'to enable the function. \nPlease enter the ' 68 'absolute path to Android SDK:') 69 _WARNING_NO_ANDROID_SDK = ('Please install the Android SDK, otherwise the ' 70 'debug function "Attach debugger to Android ' 71 'process" cannot be enabled in IntelliJ or ' 72 'Android Studio.') 73 74 def __init__(self): 75 """Initializes AndroidSDK.""" 76 self._max_api_level = 0 77 self._max_code_name = None 78 self._platform_mapping = {} 79 self._android_sdk_path = None 80 81 @property 82 def max_api_level(self): 83 """Gets the max API level.""" 84 return self._max_api_level 85 86 @property 87 def max_code_name(self): 88 """Gets the max code name.""" 89 return self._max_code_name 90 91 @property 92 def platform_mapping(self): 93 """Gets the Android platform mapping.""" 94 return self._platform_mapping 95 96 @property 97 def android_sdk_path(self): 98 """Gets the Android SDK path.""" 99 return self._android_sdk_path 100 101 def _parse_max_api_level(self): 102 """Parses the max API level from self._platform_mapping. 103 104 Returns: 105 An integer of API level and 0 means no Android platform exists. 106 """ 107 return max( 108 [v[self._API_LEVEL] for v in self._platform_mapping.values()], 109 default=0) 110 111 def _parse_max_code_name(self): 112 """Parses the max code name from self._platform_mapping. 113 114 Returns: 115 A string of code name. 116 """ 117 code_name = '' 118 for data in self._platform_mapping.values(): 119 if (data[self._API_LEVEL] == self._max_api_level 120 and data[self._CODE_NAME] > code_name): 121 code_name = data[self._CODE_NAME] 122 return code_name 123 124 def _parse_api_info(self, properties_file): 125 """Parses the API information from the source.properties file. 126 127 For the preview platform like android-Q, the source.properties file 128 contains two properties named AndroidVersion.ApiLevel, API level of 129 the platform, and AndroidVersion.CodeName such as Q, the code name of 130 the platform. 131 However, the formal platform like android-29, there is no property 132 AndroidVersion.CodeName. 133 134 Args: 135 properties_file: A path of the source.properties file. 136 137 Returns: 138 A tuple contains the API level and Code name of the 139 source.properties file. 140 API level: An integer of the platform, e.g. 29. 141 Code name: A string, e.g. 29 or Q. 142 """ 143 api_level = 0 144 properties = common_util.read_file_content(properties_file) 145 match_api_level = self._RE_API_LEVEL.search(properties) 146 if match_api_level: 147 api_level = match_api_level.group(self._API_LEVEL) 148 match_code_name = self._RE_CODE_NAME.search(properties) 149 if match_code_name: 150 code_name = match_code_name.group(self._CODE_NAME) 151 else: 152 code_name = api_level 153 return api_level, code_name 154 155 def _gen_platform_mapping(self, path): 156 """Generates the Android platforms mapping. 157 158 Args: 159 path: A string, the absolute path of Android SDK. 160 161 Returns: 162 True when successful generates platform mapping, otherwise False. 163 """ 164 prop_files = glob.glob(os.path.join(path, self._GLOB_PROPERTIES_FILE)) 165 for prop_file in prop_files: 166 api_level, code_name = self._parse_api_info(prop_file) 167 if not api_level: 168 continue 169 platform = os.path.basename(os.path.dirname(prop_file)) 170 self._platform_mapping[platform] = { 171 self._API_LEVEL: int(api_level), 172 self._CODE_NAME: code_name 173 } 174 return bool(self._platform_mapping) 175 176 def is_android_sdk_path(self, path): 177 """Checks if the Android SDK path is correct. 178 179 Confirm the Android SDK path is correct by checking if it has 180 platform versions. 181 182 Args: 183 path: A string, the path of Android SDK user input. 184 185 Returns: 186 True when get a platform version, otherwise False. 187 """ 188 if self._gen_platform_mapping(path): 189 self._android_sdk_path = path 190 self._max_api_level = self._parse_max_api_level() 191 self._max_code_name = self._parse_max_code_name() 192 return True 193 return False 194 195 def path_analysis(self, sdk_path): 196 """Analyses the Android SDK path. 197 198 Confirm the path is a Android SDK folder. If it's not correct, ask user 199 to enter a new one. Skip asking when enter nothing. 200 201 Args: 202 sdk_path: A string, the path of Android SDK. 203 204 Returns: 205 True when get an Android SDK path, otherwise False. 206 """ 207 for _ in range(self._INPUT_QUERY_TIMES): 208 if self.is_android_sdk_path(sdk_path): 209 return True 210 sdk_path = input(common_util.COLORED_FAIL( 211 self._ENTER_ANDROID_SDK_PATH.format(sdk_path))) 212 if not sdk_path: 213 break 214 print('\n{} {}\n'.format(common_util.COLORED_INFO('Warning:'), 215 self._WARNING_NO_ANDROID_SDK)) 216 return False 217