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"""Creates the iml file for each module. 18 19This class is used to create the iml file for each module. So far, only generate 20the create_srcjar() for the framework-all module. 21 22Usage example: 23 modules_info = project_info.ProjectInfo.modules_info 24 mod_info = modules_info.name_to_module_info['module'] 25 iml = IMLGenerator(mod_info) 26 iml.create() 27""" 28 29from __future__ import absolute_import 30 31import logging 32import os 33 34from aidegen import constant 35from aidegen import templates 36from aidegen.lib import common_util 37 38 39class IMLGenerator: 40 """Creates the iml file for each module. 41 42 Class attributes: 43 _USED_NAME_CACHE: A dict to cache already used iml project file names 44 and prevent duplicated iml names from breaking IDEA. 45 46 Attributes: 47 _mod_info: A dictionary of the module's data from module-info.json. 48 _android_root: A string ot the Android root's absolute path. 49 _mod_path: A string of the module's absolute path. 50 _iml_path: A string of the module's iml absolute path. 51 _facet: A string of the facet setting. 52 _excludes: A string of the exclude relative paths. 53 _srcs: A string of the source urls. 54 _jars: A list of the jar urls. 55 _srcjars: A list of srcjar urls. 56 _deps: A list of the dependency module urls. 57 """ 58 # b/121256503: Prevent duplicated iml names from breaking IDEA. 59 # Use a map to cache in-using(already used) iml project file names. 60 USED_NAME_CACHE = dict() 61 62 def __init__(self, mod_info): 63 """Initializes IMLGenerator. 64 65 Args: 66 mod_info: A dictionary of the module's data from module-info.json. 67 """ 68 self._mod_info = mod_info 69 self._android_root = common_util.get_android_root_dir() 70 self._mod_path = os.path.join(self._android_root, 71 mod_info[constant.KEY_PATH][0]) 72 self._iml_path = os.path.join(self._mod_path, 73 mod_info[constant.KEY_IML_NAME] + '.iml') 74 self._facet = '' 75 self._excludes = '' 76 self._srcs = '' 77 self._jars = [] 78 self._srcjars = [] 79 self._deps = [] 80 81 @classmethod 82 def get_unique_iml_name(cls, abs_module_path): 83 """Create a unique iml name if needed. 84 85 If the name of last sub folder is used already, prefixing it with prior 86 sub folder names as a candidate name. If finally, it's unique, storing 87 in USED_NAME_CACHE as: { abs_module_path:unique_name }. The cts case 88 and UX of IDE view are the main reasons why using module path strategy 89 but not name of module directly. Following is the detailed strategy: 90 1. While loop composes a sensible and shorter name, by checking unique 91 to finish the loop and finally add to cache. 92 Take ['cts', 'tests', 'app', 'ui'] an example, if 'ui' isn't 93 occupied, use it, else try 'cts_ui', then 'cts_app_ui', the worst 94 case is whole three candidate names are occupied already. 95 2. 'Else' for that while stands for no suitable name generated, so 96 trying 'cts_tests_app_ui' directly. If it's still non unique, e.g., 97 module path cts/xxx/tests/app/ui occupied that name already, 98 appending increasing sequence number to get a unique name. 99 100 Args: 101 abs_module_path: The absolute module path string. 102 103 Return: 104 String: A unique iml name. 105 """ 106 if abs_module_path in cls.USED_NAME_CACHE: 107 return cls.USED_NAME_CACHE[abs_module_path] 108 109 uniq_name = abs_module_path.strip(os.sep).split(os.sep)[-1] 110 if any(uniq_name == name for name in cls.USED_NAME_CACHE.values()): 111 parent_path = os.path.relpath(abs_module_path, 112 common_util.get_android_root_dir()) 113 sub_folders = parent_path.split(os.sep) 114 zero_base_index = len(sub_folders) - 1 115 # Start compose a sensible, shorter and unique name. 116 while zero_base_index > 0: 117 uniq_name = '_'.join( 118 [sub_folders[0], '_'.join(sub_folders[zero_base_index:])]) 119 zero_base_index = zero_base_index - 1 120 if uniq_name not in cls.USED_NAME_CACHE.values(): 121 break 122 else: 123 # TODO(b/133393638): To handle several corner cases. 124 uniq_name_base = parent_path.strip(os.sep).replace(os.sep, '_') 125 i = 0 126 uniq_name = uniq_name_base 127 while uniq_name in cls.USED_NAME_CACHE.values(): 128 i = i + 1 129 uniq_name = '_'.join([uniq_name_base, str(i)]) 130 cls.USED_NAME_CACHE[abs_module_path] = uniq_name 131 logging.debug('Unique name for module path of %s is %s.', 132 abs_module_path, uniq_name) 133 return uniq_name 134 135 @property 136 def iml_path(self): 137 """Gets the iml path.""" 138 return self._iml_path 139 140 def create(self, content_type): 141 """Creates the iml file. 142 143 Create the iml file with specific part of sources. 144 e.g. 145 { 146 'srcs': True, 147 'dependencies': True, 148 } 149 150 Args: 151 content_type: A dict to set which part of sources will be created. 152 """ 153 if content_type.get(constant.KEY_SRCS, None): 154 self._generate_srcs() 155 if content_type.get(constant.KEY_DEP_SRCS, None): 156 self._generate_dep_srcs() 157 if content_type.get(constant.KEY_JARS, None): 158 self._generate_jars() 159 if content_type.get(constant.KEY_SRCJARS, None): 160 self._generate_srcjars() 161 if content_type.get(constant.KEY_DEPENDENCIES, None): 162 self._generate_dependencies() 163 164 if self._srcs or self._jars or self._srcjars or self._deps: 165 self._create_iml() 166 167 def _generate_facet(self): 168 """Generates the facet when the AndroidManifest.xml exists.""" 169 if os.path.exists(os.path.join(self._mod_path, 170 constant.ANDROID_MANIFEST)): 171 self._facet = templates.FACET 172 173 def _generate_srcs(self): 174 """Generates the source urls of the project's iml file.""" 175 srcs = [] 176 for src in self._mod_info[constant.KEY_SRCS]: 177 srcs.append(templates.SOURCE.format( 178 SRC=os.path.join(self._android_root, src), 179 IS_TEST='false')) 180 for test in self._mod_info[constant.KEY_TESTS]: 181 srcs.append(templates.SOURCE.format( 182 SRC=os.path.join(self._android_root, test), 183 IS_TEST='true')) 184 self._excludes = self._mod_info.get(constant.KEY_EXCLUDES, '') 185 self._srcs = templates.CONTENT.format(MODULE_PATH=self._mod_path, 186 EXCLUDES=self._excludes, 187 SOURCES=''.join(sorted(srcs))) 188 189 def _generate_dep_srcs(self): 190 """Generates the source urls of the dependencies.iml.""" 191 srcs = [] 192 for src in self._mod_info[constant.KEY_SRCS]: 193 srcs.append(templates.OTHER_SOURCE.format( 194 SRC=os.path.join(self._android_root, src), 195 IS_TEST='false')) 196 for test in self._mod_info[constant.KEY_TESTS]: 197 srcs.append(templates.OTHER_SOURCE.format( 198 SRC=os.path.join(self._android_root, test), 199 IS_TEST='true')) 200 self._srcs = ''.join(sorted(srcs)) 201 202 def _generate_jars(self): 203 """Generates the jar urls.""" 204 for jar in self._mod_info[constant.KEY_JARS]: 205 self._jars.append(templates.JAR.format( 206 JAR=os.path.join(self._android_root, jar))) 207 208 def _generate_srcjars(self): 209 """Generates the srcjar urls.""" 210 for srcjar in self._mod_info[constant.KEY_SRCJARS]: 211 self._srcjars.append(templates.SRCJAR.format( 212 SRCJAR=os.path.join(self._android_root, srcjar))) 213 214 def _generate_dependencies(self): 215 """Generates the dependency module urls.""" 216 for dep in self._mod_info[constant.KEY_DEPENDENCIES]: 217 self._deps.append(templates.DEPENDENCIES.format(MODULE=dep)) 218 219 def _create_iml(self): 220 """Creates the iml file.""" 221 content = templates.IML.format(FACET=self._facet, 222 SOURCES=self._srcs, 223 JARS=''.join(self._jars), 224 SRCJARS=''.join(self._srcjars), 225 DEPENDENCIES=''.join(self._deps)) 226 common_util.file_generate(self._iml_path, content) 227