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 framework_srcs = [] 177 for src in self._mod_info.get(constant.KEY_SRCS, []): 178 if constant.FRAMEWORK_PATH in src: 179 framework_srcs.append(templates.SOURCE.format( 180 SRC=os.path.join(self._android_root, src), 181 IS_TEST='false')) 182 continue 183 srcs.append(templates.SOURCE.format( 184 SRC=os.path.join(self._android_root, src), 185 IS_TEST='false')) 186 for test in self._mod_info.get(constant.KEY_TESTS, []): 187 if constant.FRAMEWORK_PATH in test: 188 framework_srcs.append(templates.SOURCE.format( 189 SRC=os.path.join(self._android_root, test), 190 IS_TEST='true')) 191 continue 192 srcs.append(templates.SOURCE.format( 193 SRC=os.path.join(self._android_root, test), 194 IS_TEST='true')) 195 self._excludes = self._mod_info.get(constant.KEY_EXCLUDES, '') 196 197 #For sovling duplicate package name, frameworks/base will be higher 198 #priority. 199 srcs = sorted(framework_srcs) + sorted(srcs) 200 self._srcs = templates.CONTENT.format(MODULE_PATH=self._mod_path, 201 EXCLUDES=self._excludes, 202 SOURCES=''.join(srcs)) 203 204 def _generate_dep_srcs(self): 205 """Generates the source urls of the dependencies.iml.""" 206 srcs = [] 207 for src in self._mod_info.get(constant.KEY_SRCS, []): 208 srcs.append(templates.OTHER_SOURCE.format( 209 SRC=os.path.join(self._android_root, src), 210 IS_TEST='false')) 211 for test in self._mod_info.get(constant.KEY_TESTS, []): 212 srcs.append(templates.OTHER_SOURCE.format( 213 SRC=os.path.join(self._android_root, test), 214 IS_TEST='true')) 215 self._srcs = ''.join(sorted(srcs)) 216 217 def _generate_jars(self): 218 """Generates the jar urls.""" 219 for jar in self._mod_info.get(constant.KEY_JARS, []): 220 self._jars.append(templates.JAR.format( 221 JAR=os.path.join(self._android_root, jar))) 222 223 def _generate_srcjars(self): 224 """Generates the srcjar urls.""" 225 for srcjar in self._mod_info.get(constant.KEY_SRCJARS, []): 226 self._srcjars.append(templates.SRCJAR.format( 227 SRCJAR=os.path.join(self._android_root, srcjar))) 228 229 def _generate_dependencies(self): 230 """Generates the dependency module urls.""" 231 for dep in self._mod_info.get(constant.KEY_DEPENDENCIES, []): 232 self._deps.append(templates.DEPENDENCIES.format(MODULE=dep)) 233 234 def _create_iml(self): 235 """Creates the iml file.""" 236 content = templates.IML.format(FACET=self._facet, 237 SOURCES=self._srcs, 238 JARS=''.join(self._jars), 239 SRCJARS=''.join(self._srcjars), 240 DEPENDENCIES=''.join(self._deps)) 241 common_util.file_generate(self._iml_path, content) 242