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 = {} 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 111 if any(uniq_name == name for name in cls.USED_NAME_CACHE.values()): 112 parent_path = os.path.relpath(abs_module_path, 113 common_util.get_android_root_dir()) 114 sub_folders = parent_path.split(os.sep) 115 zero_base_index = len(sub_folders) - 1 116 # Start compose a sensible, shorter and unique name. 117 while zero_base_index > 0: 118 uniq_name = '_'.join( 119 [sub_folders[0], '_'.join(sub_folders[zero_base_index:])]) 120 zero_base_index = zero_base_index - 1 121 if uniq_name not in cls.USED_NAME_CACHE.values(): 122 break 123 else: 124 # For full source tree case, there are 2 path cases w/wo "/". 125 # In the 2nd run, if abs_module_path is root dir, 126 # it will use uniq_name directly. 127 if parent_path == ".": 128 pass 129 else: 130 # TODO(b/133393638): To handle several corner cases. 131 uniq_name_base = parent_path.strip(os.sep).replace(os.sep, 132 '_') 133 i = 0 134 uniq_name = uniq_name_base 135 while uniq_name in cls.USED_NAME_CACHE.values(): 136 i = i + 1 137 uniq_name = '_'.join([uniq_name_base, str(i)]) 138 cls.USED_NAME_CACHE[abs_module_path] = uniq_name 139 logging.debug('Unique name for module path of %s is %s.', 140 abs_module_path, uniq_name) 141 return uniq_name 142 143 @property 144 def iml_path(self): 145 """Gets the iml path.""" 146 return self._iml_path 147 148 def create(self, content_type): 149 """Creates the iml file. 150 151 Create the iml file with specific part of sources. 152 e.g. 153 { 154 'srcs': True, 155 'dependencies': True, 156 } 157 158 Args: 159 content_type: A dict to set which part of sources will be created. 160 """ 161 if content_type.get(constant.KEY_SRCS, None): 162 self._generate_srcs() 163 if content_type.get(constant.KEY_DEP_SRCS, None): 164 self._generate_dep_srcs() 165 if content_type.get(constant.KEY_JARS, None): 166 self._generate_jars() 167 if content_type.get(constant.KEY_SRCJARS, None): 168 self._generate_srcjars() 169 if content_type.get(constant.KEY_DEPENDENCIES, None): 170 self._generate_dependencies() 171 172 if self._srcs or self._jars or self._srcjars or self._deps: 173 self._create_iml() 174 175 def _generate_facet(self): 176 """Generates the facet when the AndroidManifest.xml exists.""" 177 if os.path.exists(os.path.join(self._mod_path, 178 constant.ANDROID_MANIFEST)): 179 self._facet = templates.FACET 180 181 def _generate_srcs(self): 182 """Generates the source urls of the project's iml file.""" 183 srcs = [] 184 framework_srcs = [] 185 for src in self._mod_info.get(constant.KEY_SRCS, []): 186 if constant.FRAMEWORK_PATH in src: 187 framework_srcs.append(templates.SOURCE.format( 188 SRC=os.path.join(self._android_root, src), 189 IS_TEST='false')) 190 continue 191 srcs.append(templates.SOURCE.format( 192 SRC=os.path.join(self._android_root, src), 193 IS_TEST='false')) 194 for test in self._mod_info.get(constant.KEY_TESTS, []): 195 if constant.FRAMEWORK_PATH in test: 196 framework_srcs.append(templates.SOURCE.format( 197 SRC=os.path.join(self._android_root, test), 198 IS_TEST='true')) 199 continue 200 srcs.append(templates.SOURCE.format( 201 SRC=os.path.join(self._android_root, test), 202 IS_TEST='true')) 203 self._excludes = self._mod_info.get(constant.KEY_EXCLUDES, '') 204 205 # For solving duplicate package name, frameworks/base will be higher 206 # priority. 207 srcs = sorted(framework_srcs) + sorted(srcs) 208 self._srcs = templates.CONTENT.format(MODULE_PATH=self._mod_path, 209 EXCLUDES=self._excludes, 210 SOURCES=''.join(srcs)) 211 212 def _generate_dep_srcs(self): 213 """Generates the source urls of the dependencies.iml.""" 214 srcs = [] 215 for src in self._mod_info.get(constant.KEY_SRCS, []): 216 srcs.append(templates.OTHER_SOURCE.format( 217 SRC=os.path.join(self._android_root, src), 218 IS_TEST='false')) 219 for test in self._mod_info.get(constant.KEY_TESTS, []): 220 srcs.append(templates.OTHER_SOURCE.format( 221 SRC=os.path.join(self._android_root, test), 222 IS_TEST='true')) 223 self._srcs = ''.join(sorted(srcs)) 224 225 def _generate_jars(self): 226 """Generates the jar urls.""" 227 for jar in self._mod_info.get(constant.KEY_JARS, []): 228 self._jars.append(templates.JAR.format( 229 JAR=os.path.join(self._android_root, jar))) 230 231 def _generate_srcjars(self): 232 """Generates the srcjar urls.""" 233 for srcjar in self._mod_info.get(constant.KEY_SRCJARS, []): 234 self._srcjars.append(templates.SRCJAR.format( 235 SRCJAR=os.path.join(self._android_root, srcjar))) 236 237 def _generate_dependencies(self): 238 """Generates the dependency module urls.""" 239 for dep in self._mod_info.get(constant.KEY_DEPENDENCIES, []): 240 self._deps.append(templates.DEPENDENCIES.format(MODULE=dep)) 241 242 def _create_iml(self): 243 """Creates the iml file.""" 244 content = templates.IML.format(FACET=self._facet, 245 SOURCES=self._srcs, 246 JARS=''.join(self._jars), 247 SRCJARS=''.join(self._srcjars), 248 DEPENDENCIES=''.join(self._deps)) 249 common_util.file_generate(self._iml_path, content) 250