| #!/usr/bin/env python3 |
| # |
| # Copyright 2020 - The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Creates the iml file for each module. |
| |
| This class is used to create the iml file for each module. So far, only generate |
| the create_srcjar() for the framework-all module. |
| |
| Usage example: |
| modules_info = project_info.ProjectInfo.modules_info |
| mod_info = modules_info.name_to_module_info['module'] |
| iml = IMLGenerator(mod_info) |
| iml.create() |
| """ |
| |
| from __future__ import absolute_import |
| |
| import logging |
| import os |
| |
| from aidegen import constant |
| from aidegen import templates |
| from aidegen.lib import common_util |
| |
| |
| class IMLGenerator: |
| """Creates the iml file for each module. |
| |
| Class attributes: |
| _USED_NAME_CACHE: A dict to cache already used iml project file names |
| and prevent duplicated iml names from breaking IDEA. |
| |
| Attributes: |
| _mod_info: A dictionary of the module's data from module-info.json. |
| _android_root: A string ot the Android root's absolute path. |
| _mod_path: A string of the module's absolute path. |
| _iml_path: A string of the module's iml absolute path. |
| _facet: A string of the facet setting. |
| _excludes: A string of the exclude relative paths. |
| _srcs: A string of the source urls. |
| _jars: A list of the jar urls. |
| _srcjars: A list of srcjar urls. |
| _deps: A list of the dependency module urls. |
| """ |
| # b/121256503: Prevent duplicated iml names from breaking IDEA. |
| # Use a map to cache in-using(already used) iml project file names. |
| USED_NAME_CACHE = {} |
| |
| def __init__(self, mod_info): |
| """Initializes IMLGenerator. |
| |
| Args: |
| mod_info: A dictionary of the module's data from module-info.json. |
| """ |
| self._mod_info = mod_info |
| self._android_root = common_util.get_android_root_dir() |
| self._mod_path = os.path.join(self._android_root, |
| mod_info[constant.KEY_PATH][0]) |
| self._iml_path = os.path.join(self._mod_path, |
| mod_info[constant.KEY_IML_NAME] + '.iml') |
| self._facet = '' |
| self._excludes = '' |
| self._srcs = '' |
| self._jars = [] |
| self._srcjars = [] |
| self._deps = [] |
| |
| @classmethod |
| def get_unique_iml_name(cls, abs_module_path): |
| """Create a unique iml name if needed. |
| |
| If the name of last sub folder is used already, prefixing it with prior |
| sub folder names as a candidate name. If finally, it's unique, storing |
| in USED_NAME_CACHE as: { abs_module_path:unique_name }. The cts case |
| and UX of IDE view are the main reasons why using module path strategy |
| but not name of module directly. Following is the detailed strategy: |
| 1. While loop composes a sensible and shorter name, by checking unique |
| to finish the loop and finally add to cache. |
| Take ['cts', 'tests', 'app', 'ui'] an example, if 'ui' isn't |
| occupied, use it, else try 'cts_ui', then 'cts_app_ui', the worst |
| case is whole three candidate names are occupied already. |
| 2. 'Else' for that while stands for no suitable name generated, so |
| trying 'cts_tests_app_ui' directly. If it's still non unique, e.g., |
| module path cts/xxx/tests/app/ui occupied that name already, |
| appending increasing sequence number to get a unique name. |
| |
| Args: |
| abs_module_path: The absolute module path string. |
| |
| Return: |
| String: A unique iml name. |
| """ |
| if abs_module_path in cls.USED_NAME_CACHE: |
| return cls.USED_NAME_CACHE[abs_module_path] |
| |
| uniq_name = abs_module_path.strip(os.sep).split(os.sep)[-1] |
| |
| if any(uniq_name == name for name in cls.USED_NAME_CACHE.values()): |
| parent_path = os.path.relpath(abs_module_path, |
| common_util.get_android_root_dir()) |
| sub_folders = parent_path.split(os.sep) |
| zero_base_index = len(sub_folders) - 1 |
| # Start compose a sensible, shorter and unique name. |
| while zero_base_index > 0: |
| uniq_name = '_'.join( |
| [sub_folders[0], '_'.join(sub_folders[zero_base_index:])]) |
| zero_base_index = zero_base_index - 1 |
| if uniq_name not in cls.USED_NAME_CACHE.values(): |
| break |
| else: |
| # For full source tree case, there are 2 path cases w/wo "/". |
| # In the 2nd run, if abs_module_path is root dir, |
| # it will use uniq_name directly. |
| if parent_path == ".": |
| pass |
| else: |
| # TODO(b/133393638): To handle several corner cases. |
| uniq_name_base = parent_path.strip(os.sep).replace(os.sep, |
| '_') |
| i = 0 |
| uniq_name = uniq_name_base |
| while uniq_name in cls.USED_NAME_CACHE.values(): |
| i = i + 1 |
| uniq_name = '_'.join([uniq_name_base, str(i)]) |
| cls.USED_NAME_CACHE[abs_module_path] = uniq_name |
| logging.debug('Unique name for module path of %s is %s.', |
| abs_module_path, uniq_name) |
| return uniq_name |
| |
| @property |
| def iml_path(self): |
| """Gets the iml path.""" |
| return self._iml_path |
| |
| def create(self, content_type): |
| """Creates the iml file. |
| |
| Create the iml file with specific part of sources. |
| e.g. |
| { |
| 'srcs': True, |
| 'dependencies': True, |
| } |
| |
| Args: |
| content_type: A dict to set which part of sources will be created. |
| """ |
| if content_type.get(constant.KEY_SRCS, None): |
| self._generate_srcs() |
| if content_type.get(constant.KEY_DEP_SRCS, None): |
| self._generate_dep_srcs() |
| if content_type.get(constant.KEY_JARS, None): |
| self._generate_jars() |
| if content_type.get(constant.KEY_SRCJARS, None): |
| self._generate_srcjars() |
| if content_type.get(constant.KEY_DEPENDENCIES, None): |
| self._generate_dependencies() |
| |
| if self._srcs or self._jars or self._srcjars or self._deps: |
| self._create_iml() |
| |
| def _generate_facet(self): |
| """Generates the facet when the AndroidManifest.xml exists.""" |
| if os.path.exists(os.path.join(self._mod_path, |
| constant.ANDROID_MANIFEST)): |
| self._facet = templates.FACET |
| |
| def _generate_srcs(self): |
| """Generates the source urls of the project's iml file.""" |
| srcs = [] |
| framework_srcs = [] |
| for src in self._mod_info.get(constant.KEY_SRCS, []): |
| if constant.FRAMEWORK_PATH in src: |
| framework_srcs.append(templates.SOURCE.format( |
| SRC=os.path.join(self._android_root, src), |
| IS_TEST='false')) |
| continue |
| srcs.append(templates.SOURCE.format( |
| SRC=os.path.join(self._android_root, src), |
| IS_TEST='false')) |
| for test in self._mod_info.get(constant.KEY_TESTS, []): |
| if constant.FRAMEWORK_PATH in test: |
| framework_srcs.append(templates.SOURCE.format( |
| SRC=os.path.join(self._android_root, test), |
| IS_TEST='true')) |
| continue |
| srcs.append(templates.SOURCE.format( |
| SRC=os.path.join(self._android_root, test), |
| IS_TEST='true')) |
| self._excludes = self._mod_info.get(constant.KEY_EXCLUDES, '') |
| |
| # For solving duplicate package name, frameworks/base will be higher |
| # priority. |
| srcs = sorted(framework_srcs) + sorted(srcs) |
| self._srcs = templates.CONTENT.format(MODULE_PATH=self._mod_path, |
| EXCLUDES=self._excludes, |
| SOURCES=''.join(srcs)) |
| |
| def _generate_dep_srcs(self): |
| """Generates the source urls of the dependencies.iml.""" |
| srcs = [] |
| for src in self._mod_info.get(constant.KEY_SRCS, []): |
| srcs.append(templates.OTHER_SOURCE.format( |
| SRC=os.path.join(self._android_root, src), |
| IS_TEST='false')) |
| for test in self._mod_info.get(constant.KEY_TESTS, []): |
| srcs.append(templates.OTHER_SOURCE.format( |
| SRC=os.path.join(self._android_root, test), |
| IS_TEST='true')) |
| self._srcs = ''.join(sorted(srcs)) |
| |
| def _generate_jars(self): |
| """Generates the jar urls.""" |
| for jar in self._mod_info.get(constant.KEY_JARS, []): |
| self._jars.append(templates.JAR.format( |
| JAR=os.path.join(self._android_root, jar))) |
| |
| def _generate_srcjars(self): |
| """Generates the srcjar urls.""" |
| for srcjar in self._mod_info.get(constant.KEY_SRCJARS, []): |
| self._srcjars.append(templates.SRCJAR.format( |
| SRCJAR=os.path.join(self._android_root, srcjar))) |
| |
| def _generate_dependencies(self): |
| """Generates the dependency module urls.""" |
| for dep in self._mod_info.get(constant.KEY_DEPENDENCIES, []): |
| self._deps.append(templates.DEPENDENCIES.format(MODULE=dep)) |
| |
| def _create_iml(self): |
| """Creates the iml file.""" |
| content = templates.IML.format(FACET=self._facet, |
| SOURCES=self._srcs, |
| JARS=''.join(self._jars), |
| SRCJARS=''.join(self._srcjars), |
| DEPENDENCIES=''.join(self._deps)) |
| common_util.file_generate(self._iml_path, content) |