| #!/usr/bin/env python3 |
| # |
| # Copyright 2019 - 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. |
| |
| """native_module_info |
| |
| Module Info class used to hold cached module_bp_cc_deps.json. |
| """ |
| |
| import logging |
| import os |
| import re |
| |
| from aidegen import constant |
| from aidegen.lib import common_util |
| from aidegen.lib import module_info |
| |
| _CLANG = 'clang' |
| _CPPLANG = 'clang++' |
| _MODULES = 'modules' |
| _INCLUDE_TAIL = '_genc++_headers' |
| _SRC_GEN_CHECK = r'^out/soong/.intermediates/.+/gen/.+\.(c|cc|cpp)' |
| _INC_GEN_CHECK = r'^out/soong/.intermediates/.+/gen($|/.+)' |
| |
| |
| class NativeModuleInfo(module_info.AidegenModuleInfo): |
| """Class that offers fast/easy lookup for module related details. |
| |
| Class Attributes: |
| c_lang_path: Make C files compiler path. |
| cpp_lang_path: Make C++ files compiler path. |
| """ |
| |
| c_lang_path = '' |
| cpp_lang_path = '' |
| |
| def __init__(self, force_build=False, module_file=None): |
| """Initialize the NativeModuleInfo object. |
| |
| Load up the module_bp_cc_deps.json file and initialize the helper vars. |
| """ |
| if not module_file: |
| module_file = common_util.get_blueprint_json_path( |
| constant.BLUEPRINT_CC_JSONFILE_NAME) |
| if not os.path.isfile(module_file): |
| force_build = True |
| super().__init__(force_build, module_file) |
| |
| def _load_module_info_file(self, force_build, module_file): |
| """Load the module file. |
| |
| Args: |
| force_build: Boolean to indicate if we should rebuild the |
| module_info file regardless if it's created or not. |
| module_file: String of path to file to load up. Used for testing. |
| |
| Returns: |
| Tuple of module_info_target and dict of json. |
| """ |
| if force_build: |
| self._discover_mod_file_and_target(True) |
| mod_info = common_util.get_json_dict(module_file) |
| NativeModuleInfo.c_lang_path = mod_info.get(_CLANG, '') |
| NativeModuleInfo.cpp_lang_path = mod_info.get(_CPPLANG, '') |
| name_to_module_info = mod_info.get(_MODULES, {}) |
| root_dir = common_util.get_android_root_dir() |
| module_info_target = os.path.relpath(module_file, root_dir) |
| return module_info_target, name_to_module_info |
| |
| def get_module_names_in_targets_paths(self, targets): |
| """Gets module names exist in native_module_info. |
| |
| Args: |
| targets: A list of build targets to be checked. |
| |
| Returns: |
| A list of native projects' names if native projects exist otherwise |
| return None. |
| """ |
| projects = [] |
| for target in targets: |
| if target == constant.WHOLE_ANDROID_TREE_TARGET: |
| print('Do not deal with whole source tree in native projects.') |
| continue |
| rel_path, _ = common_util.get_related_paths(self, target) |
| for path in self.path_to_module_info: |
| if path.startswith(rel_path): |
| projects.extend(self.get_module_names(path)) |
| return projects |
| |
| def get_module_includes(self, mod_name): |
| """Gets module's include paths from module name. |
| |
| The include paths contain in 'header_search_path' and |
| 'system_search_path' of all flags in native module info. |
| |
| Args: |
| mod_name: A string of module name. |
| |
| Returns: |
| A set of module include paths relative to android root. |
| """ |
| includes = set() |
| mod_info = self.name_to_module_info.get(mod_name, {}) |
| if not mod_info: |
| logging.warning('%s module name %s does not exist.', |
| common_util.COLORED_INFO('Warning:'), mod_name) |
| return includes |
| for flag in mod_info: |
| for header in (constant.KEY_HEADER, constant.KEY_SYSTEM): |
| if header in mod_info[flag]: |
| includes.update(set(mod_info[flag][header])) |
| return includes |
| |
| def is_module_need_build(self, mod_name): |
| """Checks if a module need to be built by its module name. |
| |
| If a module's source files or include files contain a path looks like, |
| 'out/soong/.intermediates/../gen/sysprop/charger.sysprop.cpp' or |
| 'out/soong/.intermediates/../android.bufferhub@1.0_genc++_headers/gen' |
| and the paths do not exist, that means the module needs to be built to |
| generate relative source or include files. |
| |
| Args: |
| mod_name: A string of module name. |
| |
| Returns: |
| A boolean, True if it needs to be generated else False. |
| """ |
| mod_info = self.name_to_module_info.get(mod_name, {}) |
| if not mod_info: |
| logging.warning('%s module name %s does not exist.', |
| common_util.COLORED_INFO('Warning:'), mod_name) |
| return False |
| if self._is_source_need_build(mod_info): |
| return True |
| if self._is_include_need_build(mod_info): |
| return True |
| return False |
| |
| def _is_source_need_build(self, mod_info): |
| """Checks if a module's source files need to be built. |
| |
| If a module's source files contain a path looks like, |
| 'out/soong/.intermediates/../gen/sysprop/charger.sysprop.cpp' |
| and the paths do not exist, that means the module needs to be built to |
| generate relative source files. |
| |
| Args: |
| mod_info: A dictionary of module info to check. |
| |
| Returns: |
| A boolean, True if it needs to be generated else False. |
| """ |
| if constant.KEY_SRCS not in mod_info: |
| return False |
| for src in mod_info[constant.KEY_SRCS]: |
| if re.search(_INC_GEN_CHECK, src) and not os.path.isfile(src): |
| return True |
| return False |
| |
| def _is_include_need_build(self, mod_info): |
| """Checks if a module needs to be built by its module name. |
| |
| If a module's include files contain a path looks like, |
| 'out/soong/.intermediates/../android.bufferhub@1.0_genc++_headers/gen' |
| and the paths do not exist, that means the module needs to be built to |
| generate relative include files. |
| |
| Args: |
| mod_info: A dictionary of module info to check. |
| |
| Returns: |
| A boolean, True if it needs to be generated else False. |
| """ |
| for flag in mod_info: |
| for header in (constant.KEY_HEADER, constant.KEY_SYSTEM): |
| if header not in mod_info[flag]: |
| continue |
| for include in mod_info[flag][header]: |
| match = re.search(_INC_GEN_CHECK, include) |
| if match and not os.path.isdir(include): |
| return True |
| return False |
| |
| def is_suite_in_compatibility_suites(self, suite, mod_info): |
| """Check if suite exists in the compatibility_suites of module-info. |
| |
| Args: |
| suite: A string of suite name. |
| mod_info: Dict of module info to check. |
| |
| Returns: |
| True if it exists in mod_info, False otherwise. |
| """ |
| raise NotImplementedError() |
| |
| def get_testable_modules(self, suite=None): |
| """Return the testable modules of the given suite name. |
| |
| Args: |
| suite: A string of suite name. Set to None to return all testable |
| modules. |
| |
| Returns: |
| List of testable modules. Empty list if non-existent. |
| If suite is None, return all the testable modules in module-info. |
| """ |
| raise NotImplementedError() |
| |
| def is_testable_module(self, mod_info): |
| """Check if module is something we can test. |
| |
| A module is testable if: |
| - it's installed, or |
| - it's a robolectric module (or shares path with one). |
| |
| Args: |
| mod_info: Dict of module info to check. |
| |
| Returns: |
| True if we can test this module, False otherwise. |
| """ |
| raise NotImplementedError() |
| |
| def has_test_config(self, mod_info): |
| """Validate if this module has a test config. |
| |
| A module can have a test config in the following manner: |
| - AndroidTest.xml at the module path. |
| - test_config be set in module-info.json. |
| - Auto-generated config via the auto_test_config key in |
| module-info.json. |
| |
| Args: |
| mod_info: Dict of module info to check. |
| |
| Returns: |
| True if this module has a test config, False otherwise. |
| """ |
| raise NotImplementedError() |
| |
| def get_robolectric_test_name(self, module_name): |
| """Returns runnable robolectric module name. |
| |
| There are at least 2 modules in every robolectric module path, return |
| the module that we can run as a build target. |
| |
| Arg: |
| module_name: String of module. |
| |
| Returns: |
| String of module that is the runnable robolectric module, None if |
| none could be found. |
| """ |
| raise NotImplementedError() |
| |
| def is_robolectric_test(self, module_name): |
| """Check if module is a robolectric test. |
| |
| A module can be a robolectric test if the specified module has their |
| class set as ROBOLECTRIC (or shares their path with a module that does). |
| |
| Args: |
| module_name: String of module to check. |
| |
| Returns: |
| True if the module is a robolectric module, else False. |
| """ |
| raise NotImplementedError() |
| |
| def is_auto_gen_test_config(self, module_name): |
| """Check if the test config file will be generated automatically. |
| |
| Args: |
| module_name: A string of the module name. |
| |
| Returns: |
| True if the test config file will be generated automatically. |
| """ |
| raise NotImplementedError() |
| |
| def is_robolectric_module(self, mod_info): |
| """Check if a module is a robolectric module. |
| |
| Args: |
| mod_info: ModuleInfo to check. |
| |
| Returns: |
| True if module is a robolectric module, False otherwise. |
| """ |
| raise NotImplementedError() |
| |
| def is_native_test(self, module_name): |
| """Check if the input module is a native test. |
| |
| Args: |
| module_name: A string of the module name. |
| |
| Returns: |
| True if the test is a native test, False otherwise. |
| """ |
| raise NotImplementedError() |