You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

242 lines
9.8 KiB

#!/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 = dict()
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:
# 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 sovling 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)