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"""Separate the sources from multiple projects.""" 18 19import os 20 21from aidegen import constant 22from aidegen.idea import iml 23from aidegen.lib import common_util 24from aidegen.lib import project_config 25 26_KEY_SOURCE_PATH = 'source_folder_path' 27_KEY_TEST_PATH = 'test_folder_path' 28_SOURCE_FOLDERS = [_KEY_SOURCE_PATH, _KEY_TEST_PATH] 29_KEY_SRCJAR_PATH = 'srcjar_path' 30_KEY_R_PATH = 'r_java_path' 31_KEY_JAR_PATH = 'jar_path' 32_EXCLUDE_ITEM = '\n <excludeFolder url="file://%s" />' 33# Temporarily exclude test-dump and src_stub folders to prevent symbols from 34# resolving failure by incorrect reference. These two folders should be removed 35# after b/136982078 is resolved. 36_EXCLUDE_FOLDERS = ['.idea', '.repo', 'art', 'bionic', 'bootable', 'build', 37 'dalvik', 'developers', 'device', 'hardware', 'kernel', 38 'libnativehelper', 'pdk', 'prebuilts', 'sdk', 'system', 39 'toolchain', 'tools', 'vendor', 'out', 40 'art/tools/ahat/src/test-dump', 41 'cts/common/device-side/device-info/src_stub'] 42 43 44class ProjectSplitter: 45 """Splits the sources from multiple projects. 46 47 It's a specific solution to deal with the source folders in multiple 48 project case. Since the IntelliJ does not allow duplicate source folders, 49 AIDEGen needs to separate the source folders for each project. The single 50 project case has no different with current structure. 51 52 Usage: 53 project_splitter = ProjectSplitter(projects) 54 55 # Find the dependencies between the projects. 56 project_splitter.get_dependencies() 57 58 # Clear the source folders for each project. 59 project_splitter.revise_source_folders() 60 61 Attributes: 62 _projects: A list of ProjectInfo. 63 _all_srcs: A dictionary contains all sources of multiple projects. 64 e.g. 65 { 66 'module_name': 'test', 67 'path': ['path/to/module'], 68 'srcs': ['src_folder1', 'src_folder2'], 69 'tests': ['test_folder1', 'test_folder2'] 70 'jars': ['jar1.jar'], 71 'srcjars': ['1.srcjar', '2.srcjar'], 72 'dependencies': ['framework_srcjars', 'base'], 73 'iml_name': '/abs/path/to/iml.iml' 74 } 75 _framework_exist: A boolean, True if framework is one of the projects. 76 _framework_iml: A string, the name of the framework's iml. 77 _full_repo: A boolean, True if loading with full Android sources. 78 _full_repo_iml: A string, the name of the Android folder's iml. 79 """ 80 def __init__(self, projects): 81 """ProjectSplitter initialize. 82 83 Args: 84 projects: A list of ProjectInfo object. 85 """ 86 self._projects = projects 87 self._all_srcs = dict(projects[0].source_path) 88 self._framework_iml = None 89 self._framework_exist = any( 90 {p.project_relative_path == constant.FRAMEWORK_PATH 91 for p in self._projects}) 92 if self._framework_exist: 93 self._framework_iml = iml.IMLGenerator.get_unique_iml_name( 94 os.path.join(common_util.get_android_root_dir(), 95 constant.FRAMEWORK_PATH)) 96 self._full_repo = project_config.ProjectConfig.get_instance().full_repo 97 if self._full_repo: 98 self._full_repo_iml = os.path.basename( 99 common_util.get_android_root_dir()) 100 101 def revise_source_folders(self): 102 """Resets the source folders of each project. 103 104 There should be no duplicate source root path in IntelliJ. The issue 105 doesn't happen in single project case. Once users choose multiple 106 projects, there could be several same source paths of different 107 projects. In order to prevent that, we should remove the source paths 108 in dependencies.iml which are duplicate with the paths in [module].iml 109 files. 110 111 Steps to prevent the duplicate source root path in IntelliJ: 112 1. Copy all sources from sub-projects to main project. 113 2. Delete the source and test folders which are not under the 114 sub-projects. 115 3. Delete the sub-projects' source and test paths from the main project. 116 """ 117 self._collect_all_srcs() 118 self._keep_local_sources() 119 self._remove_duplicate_sources() 120 121 def _collect_all_srcs(self): 122 """Copies all projects' sources to a dictionary.""" 123 for project in self._projects[1:]: 124 for key, value in project.source_path.items(): 125 self._all_srcs[key].update(value) 126 127 def _keep_local_sources(self): 128 """Removes source folders which are not under the project's path. 129 130 1. Remove the source folders which are not under the project. 131 2. Remove the duplicate project's source folders from the _all_srcs. 132 """ 133 for project in self._projects: 134 srcs = project.source_path 135 relpath = project.project_relative_path 136 is_root = not relpath 137 for key in _SOURCE_FOLDERS: 138 srcs[key] = {s for s in srcs[key] 139 if common_util.is_source_under_relative_path( 140 s, relpath) or is_root} 141 self._all_srcs[key] -= srcs[key] 142 143 def _remove_duplicate_sources(self): 144 """Removes the duplicate source folders from each sub project. 145 146 Priority processing with the longest path length, e.g. 147 frameworks/base/packages/SettingsLib must have priority over 148 frameworks/base. 149 """ 150 for child in sorted(self._projects, key=lambda k: len( 151 k.project_relative_path), reverse=True): 152 for parent in self._projects: 153 is_root = not parent.project_relative_path 154 if parent is child: 155 continue 156 if (common_util.is_source_under_relative_path( 157 child.project_relative_path, 158 parent.project_relative_path) or is_root): 159 for key in _SOURCE_FOLDERS: 160 parent.source_path[key] -= child.source_path[key] 161 162 def get_dependencies(self): 163 """Gets the dependencies between the projects. 164 165 Check if the current project's source folder exists in other projects. 166 If do, the current project is a dependency module to the other. 167 """ 168 for project in sorted(self._projects, key=lambda k: len( 169 k.project_relative_path)): 170 proj_path = project.project_relative_path 171 project.dependencies = [constant.FRAMEWORK_SRCJARS] 172 if self._framework_exist and proj_path != constant.FRAMEWORK_PATH: 173 project.dependencies.append(self._framework_iml) 174 if self._full_repo and proj_path: 175 project.dependencies.append(self._full_repo_iml) 176 srcs = (project.source_path[_KEY_SOURCE_PATH] 177 | project.source_path[_KEY_TEST_PATH]) 178 for dep_proj in sorted(self._projects, key=lambda k: len( 179 k.project_relative_path)): 180 dep_path = dep_proj.project_relative_path 181 is_root = not dep_path 182 is_child = common_util.is_source_under_relative_path(dep_path, 183 proj_path) 184 is_dep = any({s for s in srcs 185 if common_util.is_source_under_relative_path( 186 s, dep_path) or is_root}) 187 if dep_proj is project or is_child or not is_dep: 188 continue 189 dep = iml.IMLGenerator.get_unique_iml_name(os.path.join( 190 common_util.get_android_root_dir(), dep_path)) 191 if dep not in project.dependencies: 192 project.dependencies.append(dep) 193 project.dependencies.append(constant.KEY_DEPENDENCIES) 194 195 def gen_framework_srcjars_iml(self): 196 """Generates the framework-srcjars.iml. 197 198 Create the iml file with only the srcjars of module framework-all. These 199 srcjars will be separated from the modules under frameworks/base. 200 201 Returns: 202 A string of the framework_srcjars.iml's absolute path. 203 """ 204 mod = dict(self._projects[0].dep_modules[constant.FRAMEWORK_ALL]) 205 mod[constant.KEY_DEPENDENCIES] = [] 206 mod[constant.KEY_IML_NAME] = constant.FRAMEWORK_SRCJARS 207 if self._framework_exist: 208 mod[constant.KEY_DEPENDENCIES].append(self._framework_iml) 209 if self._full_repo: 210 mod[constant.KEY_DEPENDENCIES].append(self._full_repo_iml) 211 mod[constant.KEY_DEPENDENCIES].append(constant.KEY_DEPENDENCIES) 212 framework_srcjars_iml = iml.IMLGenerator(mod) 213 framework_srcjars_iml.create({constant.KEY_SRCJARS: True, 214 constant.KEY_DEPENDENCIES: True}) 215 self._all_srcs[_KEY_SRCJAR_PATH] -= set(mod[constant.KEY_SRCJARS]) 216 return framework_srcjars_iml.iml_path 217 218 def _gen_dependencies_iml(self): 219 """Generates the dependencies.iml.""" 220 mod = { 221 constant.KEY_SRCS: self._all_srcs[_KEY_SOURCE_PATH], 222 constant.KEY_TESTS: self._all_srcs[_KEY_TEST_PATH], 223 constant.KEY_JARS: self._all_srcs[_KEY_JAR_PATH], 224 constant.KEY_SRCJARS: (self._all_srcs[_KEY_R_PATH] 225 | self._all_srcs[_KEY_SRCJAR_PATH]), 226 constant.KEY_DEPENDENCIES: [constant.FRAMEWORK_SRCJARS], 227 constant.KEY_PATH: [self._projects[0].project_relative_path], 228 constant.KEY_MODULE_NAME: constant.KEY_DEPENDENCIES, 229 constant.KEY_IML_NAME: constant.KEY_DEPENDENCIES 230 } 231 if self._framework_exist: 232 mod[constant.KEY_DEPENDENCIES].append(self._framework_iml) 233 if self._full_repo: 234 mod[constant.KEY_DEPENDENCIES].append(self._full_repo_iml) 235 dep_iml = iml.IMLGenerator(mod) 236 dep_iml.create({constant.KEY_DEP_SRCS: True, 237 constant.KEY_SRCJARS: True, 238 constant.KEY_JARS: True, 239 constant.KEY_DEPENDENCIES: True}) 240 241 def gen_projects_iml(self): 242 """Generates the projects' iml file.""" 243 root_path = common_util.get_android_root_dir() 244 excludes = project_config.ProjectConfig.get_instance().exclude_paths 245 for project in self._projects: 246 relpath = project.project_relative_path 247 exclude_folders = [] 248 if not relpath: 249 exclude_folders.extend(get_exclude_content(root_path)) 250 if excludes: 251 exclude_folders.extend(get_exclude_content(root_path, excludes)) 252 mod_info = { 253 constant.KEY_EXCLUDES: ''.join(exclude_folders), 254 constant.KEY_SRCS: project.source_path[_KEY_SOURCE_PATH], 255 constant.KEY_TESTS: project.source_path[_KEY_TEST_PATH], 256 constant.KEY_DEPENDENCIES: project.dependencies, 257 constant.KEY_PATH: [relpath], 258 constant.KEY_MODULE_NAME: project.module_name, 259 constant.KEY_IML_NAME: iml.IMLGenerator.get_unique_iml_name( 260 os.path.join(root_path, relpath)) 261 } 262 dep_iml = iml.IMLGenerator(mod_info) 263 dep_iml.create({constant.KEY_SRCS: True, 264 constant.KEY_DEPENDENCIES: True}) 265 project.iml_path = dep_iml.iml_path 266 self._gen_dependencies_iml() 267 268 269def get_exclude_content(root_path, excludes=None): 270 """Get the exclude folder content list. 271 272 It returns the exclude folders content list. 273 e.g. 274 ['<excludeFolder url="file://a/.idea" />', 275 '<excludeFolder url="file://a/.repo" />'] 276 277 Args: 278 root_path: Android source file path. 279 excludes: A list of exclusive directories, the default value is None but 280 will be assigned to _EXCLUDE_FOLDERS. 281 282 Returns: 283 String: exclude folder content list. 284 """ 285 exclude_items = [] 286 if not excludes: 287 excludes = _EXCLUDE_FOLDERS 288 for folder in excludes: 289 folder_path = os.path.join(root_path, folder) 290 if os.path.isdir(folder_path): 291 exclude_items.append(_EXCLUDE_ITEM % folder_path) 292 return exclude_items 293