1#!/usr/bin/env python3
2#
3# Copyright 2019 - 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"""native_module_info
18
19Module Info class used to hold cached module_bp_cc_deps.json.
20"""
21
22import logging
23import os
24import re
25
26from aidegen import constant
27from aidegen.lib import common_util
28from aidegen.lib import module_info
29
30_CLANG = 'clang'
31_CPPLANG = 'clang++'
32_MODULES = 'modules'
33_INCLUDE_TAIL = '_genc++_headers'
34_SRC_GEN_CHECK = r'^out/soong/.intermediates/.+/gen/.+\.(c|cc|cpp)'
35_INC_GEN_CHECK = r'^out/soong/.intermediates/.+/gen($|/.+)'
36
37
38class NativeModuleInfo(module_info.AidegenModuleInfo):
39    """Class that offers fast/easy lookup for module related details.
40
41    Class Attributes:
42        c_lang_path: Make C files compiler path.
43        cpp_lang_path: Make C++ files compiler path.
44    """
45
46    c_lang_path = ''
47    cpp_lang_path = ''
48
49    def __init__(self, force_build=False, module_file=None):
50        """Initialize the NativeModuleInfo object.
51
52        Load up the module_bp_cc_deps.json file and initialize the helper vars.
53        """
54        if not module_file:
55            module_file = common_util.get_blueprint_json_path(
56                constant.BLUEPRINT_CC_JSONFILE_NAME)
57        if not os.path.isfile(module_file):
58            force_build = True
59        super().__init__(force_build, module_file)
60
61    def _load_module_info_file(self, force_build, module_file):
62        """Load the module file.
63
64        Args:
65            force_build: Boolean to indicate if we should rebuild the
66                         module_info file regardless if it's created or not.
67            module_file: String of path to file to load up. Used for testing.
68
69        Returns:
70            Tuple of module_info_target and dict of json.
71        """
72        if force_build:
73            self._discover_mod_file_and_target(True)
74        mod_info = common_util.get_json_dict(module_file)
75        NativeModuleInfo.c_lang_path = mod_info.get(_CLANG, '')
76        NativeModuleInfo.cpp_lang_path = mod_info.get(_CPPLANG, '')
77        name_to_module_info = mod_info.get(_MODULES, {})
78        root_dir = common_util.get_android_root_dir()
79        module_info_target = os.path.relpath(module_file, root_dir)
80        return module_info_target, name_to_module_info
81
82    def get_module_names_in_targets_paths(self, targets):
83        """Gets module names exist in native_module_info.
84
85        Args:
86            targets: A list of build targets to be checked.
87
88        Returns:
89            A list of native projects' names if native projects exist otherwise
90            return None.
91        """
92        projects = []
93        for target in targets:
94            if target == constant.WHOLE_ANDROID_TREE_TARGET:
95                print('Do not deal with whole source tree in native projects.')
96                continue
97            rel_path, _ = common_util.get_related_paths(self, target)
98            for path in self.path_to_module_info:
99                if common_util.is_source_under_relative_path(path, rel_path):
100                    projects.extend(self.get_module_names(path))
101        return projects
102
103    def get_module_includes(self, mod_name):
104        """Gets module's include paths from module name.
105
106        The include paths contain in 'header_search_path' and
107        'system_search_path' of all flags in native module info.
108
109        Args:
110            mod_name: A string of module name.
111
112        Returns:
113            A set of module include paths relative to android root.
114        """
115        includes = set()
116        mod_info = self.name_to_module_info.get(mod_name, {})
117        if not mod_info:
118            logging.warning('%s module name %s does not exist.',
119                            common_util.COLORED_INFO('Warning:'), mod_name)
120            return includes
121        for flag in mod_info:
122            for header in (constant.KEY_HEADER, constant.KEY_SYSTEM):
123                if header in mod_info[flag]:
124                    includes.update(set(mod_info[flag][header]))
125        return includes
126
127    def is_module_need_build(self, mod_name):
128        """Checks if a module need to be built by its module name.
129
130        If a module's source files or include files contain a path looks like,
131        'out/soong/.intermediates/../gen/sysprop/charger.sysprop.cpp' or
132        'out/soong/.intermediates/../[email protected]_genc++_headers/gen'
133        and the paths do not exist, that means the module needs to be built to
134        generate relative source or include files.
135
136        Args:
137            mod_name: A string of module name.
138
139        Returns:
140            A boolean, True if it needs to be generated else False.
141        """
142        mod_info = self.name_to_module_info.get(mod_name, {})
143        if not mod_info:
144            logging.warning('%s module name %s does not exist.',
145                            common_util.COLORED_INFO('Warning:'), mod_name)
146            return False
147        if self._is_source_need_build(mod_info):
148            return True
149        if self._is_include_need_build(mod_info):
150            return True
151        return False
152
153    def _is_source_need_build(self, mod_info):
154        """Checks if a module's source files need to be built.
155
156        If a module's source files contain a path looks like,
157        'out/soong/.intermediates/../gen/sysprop/charger.sysprop.cpp'
158        and the paths do not exist, that means the module needs to be built to
159        generate relative source files.
160
161        Args:
162            mod_info: A dictionary of module info to check.
163
164        Returns:
165            A boolean, True if it needs to be generated else False.
166        """
167        if constant.KEY_SRCS not in mod_info:
168            return False
169        for src in mod_info[constant.KEY_SRCS]:
170            if re.search(_INC_GEN_CHECK, src) and not os.path.isfile(src):
171                return True
172        return False
173
174    def _is_include_need_build(self, mod_info):
175        """Checks if a module needs to be built by its module name.
176
177        If a module's include files contain a path looks like,
178        'out/soong/.intermediates/../[email protected]_genc++_headers/gen'
179        and the paths do not exist, that means the module needs to be built to
180        generate relative include files.
181
182        Args:
183            mod_info: A dictionary of module info to check.
184
185        Returns:
186            A boolean, True if it needs to be generated else False.
187        """
188        for flag in mod_info:
189            for header in (constant.KEY_HEADER, constant.KEY_SYSTEM):
190                if header not in mod_info[flag]:
191                    continue
192                for include in mod_info[flag][header]:
193                    match = re.search(_INC_GEN_CHECK, include)
194                    if match and not os.path.isdir(include):
195                        return True
196        return False
197
198    def is_suite_in_compatibility_suites(self, suite, mod_info):
199        """Check if suite exists in the compatibility_suites of module-info.
200
201        Args:
202            suite: A string of suite name.
203            mod_info: Dict of module info to check.
204
205        Returns:
206            True if it exists in mod_info, False otherwise.
207        """
208        raise NotImplementedError()
209
210    def get_testable_modules(self, suite=None):
211        """Return the testable modules of the given suite name.
212
213        Args:
214            suite: A string of suite name. Set to None to return all testable
215            modules.
216
217        Returns:
218            List of testable modules. Empty list if non-existent.
219            If suite is None, return all the testable modules in module-info.
220        """
221        raise NotImplementedError()
222
223    def is_testable_module(self, mod_info):
224        """Check if module is something we can test.
225
226        A module is testable if:
227          - it's installed, or
228          - it's a robolectric module (or shares path with one).
229
230        Args:
231            mod_info: Dict of module info to check.
232
233        Returns:
234            True if we can test this module, False otherwise.
235        """
236        raise NotImplementedError()
237
238    def has_test_config(self, mod_info):
239        """Validate if this module has a test config.
240
241        A module can have a test config in the following manner:
242          - AndroidTest.xml at the module path.
243          - test_config be set in module-info.json.
244          - Auto-generated config via the auto_test_config key in
245            module-info.json.
246
247        Args:
248            mod_info: Dict of module info to check.
249
250        Returns:
251            True if this module has a test config, False otherwise.
252        """
253        raise NotImplementedError()
254
255    def get_robolectric_test_name(self, module_name):
256        """Returns runnable robolectric module name.
257
258        There are at least 2 modules in every robolectric module path, return
259        the module that we can run as a build target.
260
261        Arg:
262            module_name: String of module.
263
264        Returns:
265            String of module that is the runnable robolectric module, None if
266            none could be found.
267        """
268        raise NotImplementedError()
269
270    def is_robolectric_test(self, module_name):
271        """Check if module is a robolectric test.
272
273        A module can be a robolectric test if the specified module has their
274        class set as ROBOLECTRIC (or shares their path with a module that does).
275
276        Args:
277            module_name: String of module to check.
278
279        Returns:
280            True if the module is a robolectric module, else False.
281        """
282        raise NotImplementedError()
283
284    def is_auto_gen_test_config(self, module_name):
285        """Check if the test config file will be generated automatically.
286
287        Args:
288            module_name: A string of the module name.
289
290        Returns:
291            True if the test config file will be generated automatically.
292        """
293        raise NotImplementedError()
294
295    def is_robolectric_module(self, mod_info):
296        """Check if a module is a robolectric module.
297
298        Args:
299            mod_info: ModuleInfo to check.
300
301        Returns:
302            True if module is a robolectric module, False otherwise.
303        """
304        raise NotImplementedError()
305
306    def is_native_test(self, module_name):
307        """Check if the input module is a native test.
308
309        Args:
310            module_name: A string of the module name.
311
312        Returns:
313            True if the test is a native test, False otherwise.
314        """
315        raise NotImplementedError()
316