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, 44 None if the Android SDK 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 _max_folder_name: A string, the folder name corresponding 48 to the max API level. 49 _platform_mapping: A dictionary of Android platform versions mapping to 50 the API level and the Android version code name. e.g. 51 { 52 'android-29': { 53 'api_level': 29, 54 'code_name': '29', 55 }, 56 'android-Q': { 57 'api_level': 29, 58 'code_name': 'Q', 59 } 60 } 61 """ 62 63 _API_LEVEL = 'api_level' 64 _CODE_NAME = 'code_name' 65 _FOLDER_NAME = 'folder_name' 66 _RE_API_LEVEL = re.compile( 67 r'AndroidVersion\.ApiLevel=(?P<api_level>[\d]+)') 68 _RE_CODE_NAME = re.compile( 69 r'AndroidVersion\.CodeName=(?P<code_name>[a-zA-Z]+)') 70 _GLOB_PROPERTIES_FILE = os.path.join('platforms', 'android-*', 71 'source.properties') 72 _INPUT_QUERY_TIMES = 3 73 _ENTER_ANDROID_SDK_PATH = ('\nThe Android SDK folder:{} doesn\'t exist. ' 74 'The debug function "Attach debugger to Android ' 75 'process" is disabled without Android SDK in ' 76 'IntelliJ or Android Studio. Please set it up ' 77 'to enable the function. \nPlease enter the ' 78 'absolute path to Android SDK:') 79 _WARNING_NO_ANDROID_SDK = ('Please install the Android SDK, otherwise the ' 80 'debug function "Attach debugger to Android ' 81 'process" cannot be enabled in IntelliJ or ' 82 'Android Studio.') 83 84 def __init__(self): 85 """Initializes AndroidSDK.""" 86 self._max_api_level = 0 87 self._max_code_name = None 88 self._max_folder_name = None 89 self._platform_mapping = {} 90 self._android_sdk_path = None 91 92 @property 93 def max_api_level(self): 94 """Gets the max API level.""" 95 return self._max_api_level 96 97 @property 98 def max_code_name(self): 99 """Gets the max code name.""" 100 return self._max_code_name 101 102 @property 103 def max_folder_name(self): 104 """Gets the max folder name.""" 105 return self._max_folder_name 106 107 @property 108 def platform_mapping(self): 109 """Gets the Android platform mapping.""" 110 return self._platform_mapping 111 112 @property 113 def android_sdk_path(self): 114 """Gets the Android SDK path.""" 115 return self._android_sdk_path 116 117 def _parse_max_api_level(self): 118 """Parses the max API level from self._platform_mapping. 119 120 Returns: 121 An integer of API level and 0 means no Android platform exists. 122 """ 123 return max( 124 [v[self._API_LEVEL] for v in self._platform_mapping.values()], 125 default=0) 126 127 def _parse_max_code_name(self): 128 """Parses the max code name from self._platform_mapping. 129 130 Returns: 131 A string of code name. 132 """ 133 code_name = '' 134 for data in self._platform_mapping.values(): 135 if (data[self._API_LEVEL] == self._max_api_level 136 and data[self._CODE_NAME] > code_name): 137 code_name = data[self._CODE_NAME] 138 return code_name 139 140 def _get_max_folder_name(self): 141 """Gets the max folder name from self._platform_mapping. 142 143 Returns: 144 A string of the folder name corresponding to the max API level. 145 """ 146 folder_name = '' 147 for platform, data in self._platform_mapping.items(): 148 if (data[self._API_LEVEL] == self.max_api_level 149 and data[self._CODE_NAME] == self._max_code_name): 150 folder_name = platform 151 break 152 return folder_name 153 154 def _parse_api_info(self, properties_file): 155 """Parses the API information from the source.properties file. 156 157 For the preview platform like android-Q, the source.properties file 158 contains two properties named AndroidVersion.ApiLevel, API level of 159 the platform, and AndroidVersion.CodeName such as Q, the code name of 160 the platform. 161 However, the formal platform like android-29, there is no property 162 AndroidVersion.CodeName. 163 164 Args: 165 properties_file: A path of the source.properties file. 166 167 Returns: 168 A tuple contains the API level and Code name of the 169 source.properties file. 170 API level: An integer of the platform, e.g. 29. 171 Code name: A string, e.g. 29 or Q. 172 """ 173 api_level = 0 174 properties = common_util.read_file_content(properties_file) 175 match_api_level = self._RE_API_LEVEL.search(properties) 176 if match_api_level: 177 api_level = match_api_level.group(self._API_LEVEL) 178 match_code_name = self._RE_CODE_NAME.search(properties) 179 if match_code_name: 180 code_name = match_code_name.group(self._CODE_NAME) 181 else: 182 code_name = api_level 183 return api_level, code_name 184 185 def _gen_platform_mapping(self, path): 186 """Generates the Android platforms mapping. 187 188 Args: 189 path: A string, the absolute path of Android SDK. 190 191 Returns: 192 True when successful generates platform mapping, otherwise False. 193 """ 194 prop_files = glob.glob(os.path.join(path, self._GLOB_PROPERTIES_FILE)) 195 for prop_file in prop_files: 196 api_level, code_name = self._parse_api_info(prop_file) 197 if not api_level: 198 continue 199 platform = os.path.basename(os.path.dirname(prop_file)) 200 self._platform_mapping[platform] = { 201 self._API_LEVEL: int(api_level), 202 self._CODE_NAME: code_name 203 } 204 return bool(self._platform_mapping) 205 206 def is_android_sdk_path(self, path): 207 """Checks if the Android SDK path is correct. 208 209 Confirm the Android SDK path is correct by checking if it has 210 platform versions. 211 212 Args: 213 path: A string, the path of Android SDK user input. 214 215 Returns: 216 True when get a platform version, otherwise False. 217 """ 218 if self._gen_platform_mapping(path): 219 self._android_sdk_path = path 220 self._max_api_level = self._parse_max_api_level() 221 self._max_code_name = self._parse_max_code_name() 222 self._max_folder_name = self._get_max_folder_name() 223 return True 224 return False 225 226 def path_analysis(self, sdk_path): 227 """Analyses the Android SDK path. 228 229 Confirm the path is an Android SDK folder. If it's not correct, ask user 230 to enter a new one. Skip asking when enter nothing. 231 232 Args: 233 sdk_path: A string, the path of Android SDK. 234 235 Returns: 236 True when get an Android SDK path, otherwise False. 237 """ 238 for _ in range(self._INPUT_QUERY_TIMES): 239 if self.is_android_sdk_path(sdk_path): 240 return True 241 sdk_path = input(common_util.COLORED_FAIL( 242 self._ENTER_ANDROID_SDK_PATH.format(sdk_path))) 243 if not sdk_path: 244 break 245 print('\n{} {}\n'.format(common_util.COLORED_INFO('Warning:'), 246 self._WARNING_NO_ANDROID_SDK)) 247 return False 248