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