1#!/usr/bin/env python3 2# 3# Copyright 2018 - 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 class. 18 19History: 20 version 2: Record the user's each preferred ide version by the key name 21 [ide_base.ide_name]_preferred_version. E.g., the key name of the 22 preferred IntelliJ is IntelliJ_preferred_version and the example 23 is as follows. 24 "Android Studio_preferred_version": "/opt/android-studio-3.0/bin/ 25 studio.sh" 26 "IntelliJ_preferred_version": "/opt/intellij-ce-stable/bin/ 27 idea.sh" 28 29 version 1: Record the user's preferred IntelliJ version by the key name 30 preferred_version and doesn't support any other IDEs. The example 31 is "preferred_version": "/opt/intellij-ce-stable/bin/idea.sh". 32""" 33 34import copy 35import json 36import logging 37import os 38import re 39 40from aidegen import constant 41from aidegen import templates 42from aidegen.lib import common_util 43 44_DIR_LIB = 'lib' 45 46 47class AidegenConfig: 48 """Class manages AIDEGen's configurations. 49 50 Attributes: 51 _config: A dict contains the aidegen config. 52 _config_backup: A dict contains the aidegen config. 53 """ 54 55 # Constants of AIDEGen config 56 _DEFAULT_CONFIG_FILE = 'aidegen.config' 57 _CONFIG_DIR = os.path.join( 58 os.path.expanduser('~'), '.config', 'asuite', 'aidegen') 59 _CONFIG_FILE_PATH = os.path.join(_CONFIG_DIR, _DEFAULT_CONFIG_FILE) 60 _KEY_APPEND = 'preferred_version' 61 62 # Constants of enable debugger 63 _ENABLE_DEBUG_CONFIG_DIR = 'enable_debugger' 64 _ENABLE_DEBUG_CONFIG_FILE = 'enable_debugger.iml' 65 _ENABLE_DEBUG_DIR = os.path.join(_CONFIG_DIR, _ENABLE_DEBUG_CONFIG_DIR) 66 _DIR_SRC = 'src' 67 _DIR_GEN = 'gen' 68 DEBUG_ENABLED_FILE_PATH = os.path.join(_ENABLE_DEBUG_DIR, 69 _ENABLE_DEBUG_CONFIG_FILE) 70 71 # Constants of checking deprecated IntelliJ version. 72 # The launch file idea.sh of IntelliJ is in ASCII encoding. 73 ENCODE_TYPE = 'ISO-8859-1' 74 ACTIVE_KEYWORD = '$JAVA_BIN' 75 76 def __init__(self): 77 self._config = {} 78 self._config_backup = {} 79 self._create_config_folder() 80 81 def __enter__(self): 82 self._load_aidegen_config() 83 self._config_backup = copy.deepcopy(self._config) 84 return self 85 86 def __exit__(self, exc_type, exc_val, exc_tb): 87 self._save_aidegen_config() 88 89 def preferred_version(self, ide=None): 90 """AIDEGen configuration getter. 91 92 Args: 93 ide: The string of the relevant IDE name, same as the data of 94 IdeBase._ide_name or IdeUtil.ide_name(). None represents the 95 usage of the version 1. 96 97 Returns: 98 The preferred version item of configuration data if exists and is 99 not deprecated, otherwise None. 100 """ 101 key = '_'.join([ide, self._KEY_APPEND]) if ide else self._KEY_APPEND 102 preferred_version = self._config.get(key, '') 103 # Backward compatible check. 104 if not preferred_version: 105 preferred_version = self._config.get(self._KEY_APPEND, '') 106 107 if preferred_version: 108 real_version = os.path.realpath(preferred_version) 109 if ide and not self.deprecated_version(ide, real_version): 110 return preferred_version 111 # Backward compatible handling. 112 if not ide and not self.deprecated_intellij_version(real_version): 113 return preferred_version 114 return None 115 116 def set_preferred_version(self, preferred_version, ide=None): 117 """AIDEGen configuration setter. 118 119 Args: 120 preferred_version: A string, user's preferred version to be set. 121 ide: The string of the relevant IDE name, same as the data of 122 IdeBase._ide_name or IdeUtil.ide_name(). None presents the 123 usage of the version 1. 124 """ 125 key = '_'.join([ide, self._KEY_APPEND]) if ide else self._KEY_APPEND 126 self._config[key] = preferred_version 127 128 def _load_aidegen_config(self): 129 """Load data from configuration file.""" 130 if os.path.exists(self._CONFIG_FILE_PATH): 131 try: 132 with open(self._CONFIG_FILE_PATH) as cfg_file: 133 self._config = json.load(cfg_file) 134 except ValueError as err: 135 info = '{} format is incorrect, error: {}'.format( 136 self._CONFIG_FILE_PATH, err) 137 logging.info(info) 138 except IOError as err: 139 logging.error(err) 140 raise 141 142 def _save_aidegen_config(self): 143 """Save data to configuration file.""" 144 if self._is_config_modified(): 145 with open(self._CONFIG_FILE_PATH, 'w') as cfg_file: 146 json.dump(self._config, cfg_file, indent=4) 147 148 def _is_config_modified(self): 149 """Check if configuration data is modified.""" 150 return any(key for key in self._config if not key in self._config_backup 151 or self._config[key] != self._config_backup[key]) 152 153 def _create_config_folder(self): 154 """Create the config folder if it doesn't exist.""" 155 if not os.path.exists(self._CONFIG_DIR): 156 os.makedirs(self._CONFIG_DIR) 157 158 def _gen_enable_debug_sub_dir(self, dir_name): 159 """Generate a dir under enable debug dir. 160 161 Args: 162 dir_name: A string of the folder name. 163 """ 164 _dir = os.path.join(self._ENABLE_DEBUG_DIR, dir_name) 165 if not os.path.exists(_dir): 166 os.makedirs(_dir) 167 168 def _gen_androidmanifest(self): 169 """Generate an AndroidManifest.xml under enable debug dir. 170 171 Once the AndroidManifest.xml does not exist or file size is zero, 172 AIDEGen will generate it with default content to prevent the red 173 underline error in IntelliJ. 174 """ 175 _file = os.path.join(self._ENABLE_DEBUG_DIR, constant.ANDROID_MANIFEST) 176 if not os.path.exists(_file) or os.stat(_file).st_size == 0: 177 common_util.file_generate(_file, templates.ANDROID_MANIFEST_CONTENT) 178 179 def _gen_enable_debugger_config(self, android_sdk_version): 180 """Generate the enable_debugger.iml config file. 181 182 Re-generate the enable_debugger.iml everytime for correcting the Android 183 SDK version. 184 185 Args: 186 android_sdk_version: The version name of the Android Sdk in the 187 jdk.table.xml. 188 """ 189 content = templates.XML_ENABLE_DEBUGGER.format( 190 ANDROID_SDK_VERSION=android_sdk_version) 191 common_util.file_generate(self.DEBUG_ENABLED_FILE_PATH, content) 192 193 def create_enable_debugger_module(self, android_sdk_version): 194 """Create the enable_debugger module. 195 196 1. Create two empty folders named src and gen. 197 2. Create an empty file named AndroidManifest.xml 198 3. Create the enable_denugger.iml. 199 200 Args: 201 android_sdk_version: The version name of the Android Sdk in the 202 jdk.table.xml. 203 204 Returns: True if successfully generate the enable debugger module, 205 otherwise False. 206 """ 207 try: 208 self._gen_enable_debug_sub_dir(self._DIR_SRC) 209 self._gen_enable_debug_sub_dir(self._DIR_GEN) 210 self._gen_androidmanifest() 211 self._gen_enable_debugger_config(android_sdk_version) 212 return True 213 except (IOError, OSError) as err: 214 logging.warning(('Can\'t create the enable_debugger module in %s.\n' 215 '%s'), self._CONFIG_DIR, err) 216 return False 217 218 @staticmethod 219 def deprecated_version(ide, script_path): 220 """Check if the script_path belongs to a deprecated IDE version. 221 222 Args: 223 ide: The string of the relevant IDE name, same as the data of 224 IdeBase._ide_name or IdeUtil.ide_name(). 225 script_path: The path string of the IDE script file. 226 227 Returns: True if the preferred version is deprecated, otherwise False. 228 """ 229 if ide == constant.IDE_ANDROID_STUDIO: 230 return AidegenConfig.deprecated_studio_version(script_path) 231 if ide == constant.IDE_INTELLIJ: 232 return AidegenConfig.deprecated_intellij_version(script_path) 233 return False 234 235 @staticmethod 236 def deprecated_intellij_version(idea_path): 237 """Check if the preferred IntelliJ version is deprecated or not. 238 239 The IntelliJ version is deprecated once the string "$JAVA_BIN" doesn't 240 exist in the idea.sh. 241 242 Args: 243 idea_path: the absolute path to idea.sh. 244 245 Returns: True if the preferred version was deprecated, otherwise False. 246 """ 247 if os.path.isfile(idea_path): 248 file_content = common_util.read_file_content( 249 idea_path, AidegenConfig.ENCODE_TYPE) 250 return AidegenConfig.ACTIVE_KEYWORD not in file_content 251 return False 252 253 @staticmethod 254 def deprecated_studio_version(script_path): 255 """Check if the preferred Studio version is deprecated or not. 256 257 The Studio version is deprecated once the /android-studio-*/lib folder 258 doesn't exist. 259 260 Args: 261 script_path: the absolute path to the ide script file. 262 263 Returns: True if the preferred version is deprecated, otherwise False. 264 """ 265 if not os.path.isfile(script_path): 266 return True 267 script_dir = os.path.dirname(script_path) 268 if not os.path.isdir(script_dir): 269 return True 270 lib_path = os.path.join(os.path.dirname(script_dir), _DIR_LIB) 271 return not os.path.isdir(lib_path) 272 273 274class IdeaProperties: 275 """Class manages IntelliJ's idea.properties attribute. 276 277 Class Attributes: 278 _PROPERTIES_FILE: The property file name of IntelliJ. 279 _KEY_FILESIZE: The key name of the maximun file size. 280 _FILESIZE_LIMIT: The value to be set as the max file size. 281 _RE_SEARCH_FILESIZE: A regular expression to find the current max file 282 size. 283 _PROPERTIES_CONTENT: The default content of idea.properties to be 284 generated. 285 286 Attributes: 287 idea_file: The absolute path of the idea.properties. 288 For example: 289 In Linux, it is ~/.IdeaIC2019.1/config/idea.properties. 290 In Mac, it is ~/Library/Preferences/IdeaIC2019.1/ 291 idea.properties. 292 """ 293 294 # Constants of idea.properties 295 _PROPERTIES_FILE = 'idea.properties' 296 _KEY_FILESIZE = 'idea.max.intellisense.filesize' 297 _FILESIZE_LIMIT = 100000 298 _RE_SEARCH_FILESIZE = r'%s\s?=\s?(?P<value>\d+)' % _KEY_FILESIZE 299 _PROPERTIES_CONTENT = """# custom IntelliJ IDEA properties 300 301#------------------------------------------------------------------------------- 302# Maximum size of files (in kilobytes) for which IntelliJ IDEA provides coding 303# assistance. Coding assistance for large files can affect editor performance 304# and increase memory consumption. 305# The default value is 2500. 306#------------------------------------------------------------------------------- 307idea.max.intellisense.filesize=100000 308""" 309 310 def __init__(self, config_dir): 311 """IdeaProperties initialize. 312 313 Args: 314 config_dir: The absolute dir of the idea.properties. 315 """ 316 self.idea_file = os.path.join(config_dir, self._PROPERTIES_FILE) 317 318 def _set_default_idea_properties(self): 319 """Create the file idea.properties.""" 320 common_util.file_generate(self.idea_file, self._PROPERTIES_CONTENT) 321 322 def _reset_max_file_size(self): 323 """Reset the max file size value in the idea.properties.""" 324 updated_flag = False 325 properties = common_util.read_file_content(self.idea_file).splitlines() 326 for index, line in enumerate(properties): 327 res = re.search(self._RE_SEARCH_FILESIZE, line) 328 if res and int(res.group('value')) < self._FILESIZE_LIMIT: 329 updated_flag = True 330 properties[index] = '%s=%s' % (self._KEY_FILESIZE, 331 str(self._FILESIZE_LIMIT)) 332 if updated_flag: 333 common_util.file_generate(self.idea_file, '\n'.join(properties)) 334 335 def set_max_file_size(self): 336 """Set the max file size parameter in the idea.properties.""" 337 if not os.path.exists(self.idea_file): 338 self._set_default_idea_properties() 339 else: 340 self._reset_max_file_size() 341