• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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