• 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
111        if any(uniq_name == name for name in cls.USED_NAME_CACHE.values()):
112            parent_path = os.path.relpath(abs_module_path,
113                                          common_util.get_android_root_dir())
114            sub_folders = parent_path.split(os.sep)
115            zero_base_index = len(sub_folders) - 1
116            # Start compose a sensible, shorter and unique name.
117            while zero_base_index > 0:
118                uniq_name = '_'.join(
119                    [sub_folders[0], '_'.join(sub_folders[zero_base_index:])])
120                zero_base_index = zero_base_index - 1
121                if uniq_name not in cls.USED_NAME_CACHE.values():
122                    break
123            else:
124                # For full source tree case, there are 2 path cases w/wo "/".
125                # In the 2nd run, if abs_module_path is root dir,
126                # it will use uniq_name directly.
127                if parent_path == ".":
128                    pass
129                else:
130                    # TODO(b/133393638): To handle several corner cases.
131                    uniq_name_base = parent_path.strip(os.sep).replace(os.sep,
132                                                                       '_')
133                    i = 0
134                    uniq_name = uniq_name_base
135                    while uniq_name in cls.USED_NAME_CACHE.values():
136                        i = i + 1
137                        uniq_name = '_'.join([uniq_name_base, str(i)])
138        cls.USED_NAME_CACHE[abs_module_path] = uniq_name
139        logging.debug('Unique name for module path of %s is %s.',
140                      abs_module_path, uniq_name)
141        return uniq_name
142
143    @property
144    def iml_path(self):
145        """Gets the iml path."""
146        return self._iml_path
147
148    def create(self, content_type):
149        """Creates the iml file.
150
151        Create the iml file with specific part of sources.
152        e.g.
153        {
154            'srcs': True,
155            'dependencies': True,
156        }
157
158        Args:
159            content_type: A dict to set which part of sources will be created.
160        """
161        if content_type.get(constant.KEY_SRCS, None):
162            self._generate_srcs()
163        if content_type.get(constant.KEY_DEP_SRCS, None):
164            self._generate_dep_srcs()
165        if content_type.get(constant.KEY_JARS, None):
166            self._generate_jars()
167        if content_type.get(constant.KEY_SRCJARS, None):
168            self._generate_srcjars()
169        if content_type.get(constant.KEY_DEPENDENCIES, None):
170            self._generate_dependencies()
171
172        if self._srcs or self._jars or self._srcjars or self._deps:
173            self._create_iml()
174
175    def _generate_facet(self):
176        """Generates the facet when the AndroidManifest.xml exists."""
177        if os.path.exists(os.path.join(self._mod_path,
178                                       constant.ANDROID_MANIFEST)):
179            self._facet = templates.FACET
180
181    def _generate_srcs(self):
182        """Generates the source urls of the project's iml file."""
183        srcs = []
184        framework_srcs = []
185        for src in self._mod_info.get(constant.KEY_SRCS, []):
186            if constant.FRAMEWORK_PATH in src:
187                framework_srcs.append(templates.SOURCE.format(
188                    SRC=os.path.join(self._android_root, src),
189                    IS_TEST='false'))
190                continue
191            srcs.append(templates.SOURCE.format(
192                SRC=os.path.join(self._android_root, src),
193                IS_TEST='false'))
194        for test in self._mod_info.get(constant.KEY_TESTS, []):
195            if constant.FRAMEWORK_PATH in test:
196                framework_srcs.append(templates.SOURCE.format(
197                    SRC=os.path.join(self._android_root, test),
198                    IS_TEST='true'))
199                continue
200            srcs.append(templates.SOURCE.format(
201                SRC=os.path.join(self._android_root, test),
202                IS_TEST='true'))
203        self._excludes = self._mod_info.get(constant.KEY_EXCLUDES, '')
204
205        #For sovling duplicate package name, frameworks/base will be higher
206        #priority.
207        srcs = sorted(framework_srcs) + sorted(srcs)
208        self._srcs = templates.CONTENT.format(MODULE_PATH=self._mod_path,
209                                              EXCLUDES=self._excludes,
210                                              SOURCES=''.join(srcs))
211
212    def _generate_dep_srcs(self):
213        """Generates the source urls of the dependencies.iml."""
214        srcs = []
215        for src in self._mod_info.get(constant.KEY_SRCS, []):
216            srcs.append(templates.OTHER_SOURCE.format(
217                SRC=os.path.join(self._android_root, src),
218                IS_TEST='false'))
219        for test in self._mod_info.get(constant.KEY_TESTS, []):
220            srcs.append(templates.OTHER_SOURCE.format(
221                SRC=os.path.join(self._android_root, test),
222                IS_TEST='true'))
223        self._srcs = ''.join(sorted(srcs))
224
225    def _generate_jars(self):
226        """Generates the jar urls."""
227        for jar in self._mod_info.get(constant.KEY_JARS, []):
228            self._jars.append(templates.JAR.format(
229                JAR=os.path.join(self._android_root, jar)))
230
231    def _generate_srcjars(self):
232        """Generates the srcjar urls."""
233        for srcjar in self._mod_info.get(constant.KEY_SRCJARS, []):
234            self._srcjars.append(templates.SRCJAR.format(
235                SRCJAR=os.path.join(self._android_root, srcjar)))
236
237    def _generate_dependencies(self):
238        """Generates the dependency module urls."""
239        for dep in self._mod_info.get(constant.KEY_DEPENDENCIES, []):
240            self._deps.append(templates.DEPENDENCIES.format(MODULE=dep))
241
242    def _create_iml(self):
243        """Creates the iml file."""
244        content = templates.IML.format(FACET=self._facet,
245                                       SOURCES=self._srcs,
246                                       JARS=''.join(self._jars),
247                                       SRCJARS=''.join(self._srcjars),
248                                       DEPENDENCIES=''.join(self._deps))
249        common_util.file_generate(self._iml_path, content)
250