AIDEGen: Generate source files for a native project.
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.
Bug: 151137159
Test: 1. m aidegen
2. aidegen-dev libui -v
CLion will be launched and no error in the Terminal.
3. aidegen-dev frameworks/native -v
Select 2. C/C++
CLion will be launched and no error in the Terminal.
Change-Id: I12e2c0cc23a73b8360f3f88cdbead8bdfe19cb0c
diff --git a/aidegen/lib/native_module_info.py b/aidegen/lib/native_module_info.py
index 440b04f..6388e91 100644
--- a/aidegen/lib/native_module_info.py
+++ b/aidegen/lib/native_module_info.py
@@ -21,6 +21,7 @@
import logging
import os
+import re
from aidegen import constant
from aidegen.lib import common_util
@@ -30,6 +31,8 @@
_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):
@@ -121,32 +124,76 @@
includes.update(set(mod_info[flag][header]))
return includes
- def get_gen_includes(self, mod_name):
- """Gets module's include paths which need to be generated.
+ def is_module_need_build(self, mod_name):
+ """Checks if a module need to be built by its module name.
- Gets module's include paths which don't exist, e.g.,
- 'out/soong/../android.frameworks.bufferhub@1.0_genc++_headers/gen'
- if the path doesn't exist we should generate the header files in it.
- In this example, if module 'android.frameworks.bufferhub@1.0' exists in
- native module info, we have to build it to generate include header files
- for the native module.
+ 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 set of rebuild target names.
+ A boolean, True if it needs to be generated else False.
"""
- android_root_dir = common_util.get_android_root_dir()
- includes = self.get_module_includes(mod_name)
- mod_names = set()
- for include in includes:
- if not os.path.isdir(os.path.join(android_root_dir, include)):
- target = os.path.basename(os.path.dirname(include))
- target = target.rstrip(_INCLUDE_TAIL)
- if target in self.name_to_module_info:
- mod_names.add(target)
- return mod_names
+ 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.
diff --git a/aidegen/lib/native_module_info_unittest.py b/aidegen/lib/native_module_info_unittest.py
index 13fa4ec..7f9c63b 100644
--- a/aidegen/lib/native_module_info_unittest.py
+++ b/aidegen/lib/native_module_info_unittest.py
@@ -16,7 +16,8 @@
"""Unittests for native_module_info."""
-import os.path
+import logging
+import os
import unittest
from unittest import mock
@@ -35,13 +36,17 @@
_NATIVE_INCLUDES1 = [
'frameworks/native/include',
'frameworks/native/libs/ui',
- 'out/frameworks/1.0/' + _REBUILD_TARGET1 + '_genc++_headers/gen'
+ 'out/soong/.intermediates/' + _REBUILD_TARGET1 + '_genc++_headers/gen'
]
_CC_NAME_TO_MODULE_INFO = {
'multiarch': {
'path': [
'shared/path/to/be/used2'
],
+ 'srcs': [
+ 'shared/path/to/be/used2/multiarch.cpp',
+ 'out/soong/.intermediates/shared/path/to/be/used2/gen/Iarch.cpp'
+ ],
'local_common_flags': {
constant.KEY_HEADER: _NATIVE_INCLUDES1
},
@@ -103,23 +108,154 @@
@mock.patch.object(
native_module_info.NativeModuleInfo, '_load_module_info_file')
def test_get_module_includes_empty(self, mock_load):
- """Test get_module_includes handling."""
+ """Test get_module_includes without include paths."""
mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
mod_info = native_module_info.NativeModuleInfo()
result = mod_info.get_module_includes('multiarch1')
self.assertEqual(set(), result)
@mock.patch.object(
- native_module_info.NativeModuleInfo, 'get_module_includes')
+ native_module_info.NativeModuleInfo, '_load_module_info_file')
+ def test_get_module_includes_not_empty(self, mock_load):
+ """Test get_module_includes with include paths."""
+ mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
+ mod_info = native_module_info.NativeModuleInfo()
+ result = mod_info.get_module_includes('multiarch')
+ self.assertEqual(set(_NATIVE_INCLUDES1), result)
+
+ @mock.patch.object(logging, 'warning')
@mock.patch.object(
native_module_info.NativeModuleInfo, '_load_module_info_file')
- def test_get_gen_includes_empty(self, mock_load, mock_get_includes):
- """Test get_gen_includes handling."""
+ def test_is_module_need_build_without_mod_info(self, mock_load, mock_warn):
+ """Test get_module_includes without module info."""
mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
- mock_get_includes.return_value = set()
mod_info = native_module_info.NativeModuleInfo()
- result = mod_info.get_gen_includes('multiarch1')
- self.assertEqual(set(), result)
+ self.assertFalse(mod_info.is_module_need_build('test_multiarch'))
+ self.assertTrue(mock_warn.called)
+
+ @mock.patch.object(logging, 'warning')
+ @mock.patch.object(
+ native_module_info.NativeModuleInfo, '_load_module_info_file')
+ def test_is_module_need_build_with_mod_info(self, mock_load, mock_warn):
+ """Test get_module_includes with module info."""
+ mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
+ mod_info = native_module_info.NativeModuleInfo()
+ self.assertFalse(mod_info.is_module_need_build('multiarch1'))
+ self.assertFalse(mock_warn.called)
+
+ @mock.patch('native_module_info.NativeModuleInfo._is_include_need_build')
+ @mock.patch('native_module_info.NativeModuleInfo._is_source_need_build')
+ @mock.patch.object(logging, 'warning')
+ @mock.patch.object(
+ native_module_info.NativeModuleInfo, '_load_module_info_file')
+ def test_is_module_need_build_with_src_needs(
+ self, mock_load, mock_warn, mock_src_need, mock_inc_need):
+ """Test get_module_includes with needed build source modules."""
+ mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
+ mod_info = native_module_info.NativeModuleInfo()
+ mock_src_need.return_value = True
+ mock_inc_need.return_value = False
+ self.assertTrue(mod_info.is_module_need_build('multiarch'))
+ self.assertFalse(mock_warn.called)
+
+ @mock.patch('native_module_info.NativeModuleInfo._is_include_need_build')
+ @mock.patch('native_module_info.NativeModuleInfo._is_source_need_build')
+ @mock.patch.object(logging, 'warning')
+ @mock.patch.object(
+ native_module_info.NativeModuleInfo, '_load_module_info_file')
+ def test_is_module_need_build_with_inc_needs(
+ self, mock_load, mock_warn, mock_src_need, mock_inc_need):
+ """Test get_module_includes with needed build include modules."""
+ mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
+ mod_info = native_module_info.NativeModuleInfo()
+ mock_src_need.return_value = False
+ mock_inc_need.return_value = True
+ self.assertTrue(mod_info.is_module_need_build('multiarch'))
+ self.assertFalse(mock_warn.called)
+
+ @mock.patch('native_module_info.NativeModuleInfo._is_include_need_build')
+ @mock.patch('native_module_info.NativeModuleInfo._is_source_need_build')
+ @mock.patch.object(logging, 'warning')
+ @mock.patch.object(
+ native_module_info.NativeModuleInfo, '_load_module_info_file')
+ def test_is_module_need_build_without_needs(
+ self, mock_load, mock_warn, mock_src_need, mock_inc_need):
+ """Test get_module_includes without needs."""
+ mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
+ mod_info = native_module_info.NativeModuleInfo()
+ mock_src_need.return_value = False
+ mock_inc_need.return_value = False
+ self.assertFalse(mod_info.is_module_need_build('multiarch1'))
+ self.assertFalse(mock_warn.called)
+
+ @mock.patch.object(os.path, 'isfile')
+ @mock.patch.object(
+ native_module_info.NativeModuleInfo, '_load_module_info_file')
+ def test_is_source_need_build_return_true(self, mock_load, mock_isfile):
+ """Test _is_source_need_build with source paths or not existing."""
+ mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
+ mod_info = native_module_info.NativeModuleInfo()
+ mock_isfile.return_value = False
+ self.assertTrue(mod_info._is_source_need_build(
+ _CC_NAME_TO_MODULE_INFO['multiarch']))
+
+ @mock.patch.object(os.path, 'isfile')
+ @mock.patch.object(
+ native_module_info.NativeModuleInfo, '_load_module_info_file')
+ def test_is_source_need_build_return_false(self, mock_load, mock_isfile):
+ """Test _is_source_need_build without source paths or paths exist."""
+ mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
+ mod_info = native_module_info.NativeModuleInfo()
+ self.assertFalse(mod_info._is_source_need_build(
+ _CC_NAME_TO_MODULE_INFO['multiarch1']))
+ mock_isfile.return_value = True
+ self.assertFalse(mod_info._is_source_need_build(
+ _CC_NAME_TO_MODULE_INFO['multiarch']))
+
+ @mock.patch.object(os.path, 'isdir')
+ @mock.patch.object(
+ native_module_info.NativeModuleInfo, '_load_module_info_file')
+ def test_is_include_need_build_return_true(self, mock_load, mock_isdir):
+ """Test _is_include_need_build with include paths and not existing."""
+ mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
+ mock_isdir.return_value = False
+ mod_info = native_module_info.NativeModuleInfo()
+ self.assertTrue(mod_info._is_include_need_build(
+ _CC_NAME_TO_MODULE_INFO['multiarch']))
+
+ @mock.patch.object(os.path, 'isdir')
+ @mock.patch.object(
+ native_module_info.NativeModuleInfo, '_load_module_info_file')
+ def test_is_include_need_build_return_false(self, mock_load, mock_isdir):
+ """Test _is_include_need_build without include paths or paths exist."""
+ mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
+ mod_info = native_module_info.NativeModuleInfo()
+ self.assertFalse(mod_info._is_include_need_build(
+ _CC_NAME_TO_MODULE_INFO['multiarch1']))
+ mock_isdir.return_value = True
+ self.assertFalse(mod_info._is_include_need_build(
+ _CC_NAME_TO_MODULE_INFO['multiarch']))
+
+ def test_not_implemented_methods(self):
+ """Test not implemented methods."""
+ mod_info = native_module_info.NativeModuleInfo()
+ target = 'test'
+ with self.assertRaises(NotImplementedError):
+ mod_info.get_testable_modules()
+ with self.assertRaises(NotImplementedError):
+ mod_info.is_testable_module(mock.Mock())
+ with self.assertRaises(NotImplementedError):
+ mod_info.has_test_config(mock.Mock())
+ with self.assertRaises(NotImplementedError):
+ mod_info.get_robolectric_test_name(target)
+ with self.assertRaises(NotImplementedError):
+ mod_info.is_robolectric_test(target)
+ with self.assertRaises(NotImplementedError):
+ mod_info.is_auto_gen_test_config(target)
+ with self.assertRaises(NotImplementedError):
+ mod_info.is_robolectric_module(mock.Mock())
+ with self.assertRaises(NotImplementedError):
+ mod_info.is_native_test(target)
@mock.patch.object(
native_module_info.NativeModuleInfo, '_load_module_info_file')
diff --git a/aidegen/lib/native_project_info.py b/aidegen/lib/native_project_info.py
index b955fc0..99eadb5 100644
--- a/aidegen/lib/native_project_info.py
+++ b/aidegen/lib/native_project_info.py
@@ -18,7 +18,6 @@
from __future__ import absolute_import
-from aidegen.lib import common_util
from aidegen.lib import native_module_info
from aidegen.lib import project_config
from aidegen.lib import project_info
@@ -32,8 +31,8 @@
a dictionary of module_bp_cc_deps.json.
Attributes:
- module_name: A string of the native project's module name.
- gen_includes: A set of the include paths have to be generated.
+ module_names: A list of the native project's module names.
+ need_builds: A set of module names need to be built.
"""
modules_info = None
@@ -51,17 +50,14 @@
'android.frameworks.bufferhub@1.0_genc++_headers/gen'
direcotry.
"""
- self._init_modules_info()
- _, abs_path = common_util.get_related_paths(
- NativeProjectInfo.modules_info, target)
- self.module_name = project_info.ProjectInfo.get_target_name(
- target, abs_path)
- self.gen_includes = NativeProjectInfo.modules_info.get_gen_includes(
- self.module_name)
- config = project_config.ProjectConfig.get_instance()
- if not config.is_skip_build and self.gen_includes:
- project_info.batch_build_dependencies(
- config.verbose, self.gen_includes)
+ self.module_names = [target] if self.modules_info.is_module(
+ target) else self.modules_info.get_module_names_in_targets_paths(
+ [target])
+ self.need_builds = {
+ mod_name
+ for mod_name in self.module_names
+ if self.modules_info.is_module_need_build(mod_name)
+ }
@classmethod
def _init_modules_info(cls):
@@ -70,15 +66,48 @@
return
cls.modules_info = native_module_info.NativeModuleInfo()
- @staticmethod
- def generate_projects(targets):
+ @classmethod
+ def generate_projects(cls, targets):
"""Generates a list of projects in one time by a list of module names.
+ The method will collect all needed to build modules and build their
+ source and include files for them. But if users set the skip build flag
+ it won't build anything.
+ Usage:
+ Call this method before native IDE project files are generated.
+ For example,
+ native_project_info.NativeProjectInfo.generate_projects(targets)
+ native_project_file = native_util.generate_clion_projects(targets)
+ ...
+
Args:
- targets: A list of native modules or project paths from users' input
- will be checked if they contain include paths need to be generated.
+ targets: A list of native modules or project paths which will be
+ checked if they contain source or include paths need to be
+ generated.
+ """
+ config = project_config.ProjectConfig.get_instance()
+ if config.is_skip_build:
+ return
+ cls._init_modules_info()
+ need_builds = cls._get_need_builds(targets)
+ if need_builds:
+ project_info.batch_build_dependencies(config.verbose, need_builds)
+
+ @classmethod
+ def _get_need_builds(cls, targets):
+ """Gets need to be built modules from targets.
+
+ Args:
+ targets: A list of native modules or project paths which will be
+ checked if they contain source or include paths need to be
+ generated.
Returns:
- List: A list of ProjectInfo instances.
+ A set of module names which need to be built.
"""
- return [NativeProjectInfo(target) for target in targets]
+ need_builds = set()
+ for target in targets:
+ project = NativeProjectInfo(target)
+ if project.need_builds:
+ need_builds.update(project.need_builds)
+ return need_builds
diff --git a/aidegen/lib/native_project_info_unittest.py b/aidegen/lib/native_project_info_unittest.py
index ffbaa92..4d8967f 100644
--- a/aidegen/lib/native_project_info_unittest.py
+++ b/aidegen/lib/native_project_info_unittest.py
@@ -19,7 +19,6 @@
import unittest
from unittest import mock
-from aidegen.lib import common_util
from aidegen.lib import native_module_info
from aidegen.lib import native_project_info
from aidegen.lib import project_config
@@ -40,37 +39,40 @@
self.assertEqual(mock_mod_info.call_count, 1)
@mock.patch.object(project_info, 'batch_build_dependencies')
- @mock.patch.object(
- project_config.ProjectConfig, 'get_instance')
- @mock.patch.object(project_info.ProjectInfo, 'get_target_name')
- @mock.patch.object(common_util, 'get_related_paths')
- @mock.patch.object(
- native_project_info.NativeProjectInfo, '_init_modules_info')
- def test_init(self, mock_mod_info, mock_get_rel, mock_get_name,
- mock_get_instance, mock_build):
+ @mock.patch.object(native_project_info.NativeProjectInfo,
+ '_get_need_builds')
+ @mock.patch.object(native_project_info.NativeProjectInfo,
+ '_init_modules_info')
+ @mock.patch.object(project_config.ProjectConfig, 'get_instance')
+ def test_generate_projects(self, mock_get_inst, mock_mod_info,
+ mock_get_need, mock_batch):
"""Test initializing NativeProjectInfo woth different conditions."""
target = 'libui'
- mock_mod_info.return_value = None
- mock_get_rel.return_value = 'rel_path', 'path_to_something'
- mock_get_name.return_value = target
- mod_info = mock.Mock()
- native_project_info.NativeProjectInfo.modules_info = mod_info
config = mock.Mock()
- mock_get_instance.return_value = config
+ mock_get_inst.return_value = config
config.is_skip_build = True
- native_project_info.NativeProjectInfo(target)
- self.assertFalse(mock_build.called)
- mod_info.get_gen_includes.return_value = None
+ native_project_info.NativeProjectInfo.generate_projects([target])
+ self.assertFalse(mock_mod_info.called)
+
+ mock_mod_info.reset_mock()
config.is_skip_build = False
- native_project_info.NativeProjectInfo(target)
- self.assertFalse(mock_build.called)
- mod_info.get_gen_includes.return_value = ['path_to_include']
- config.is_skip_build = True
- native_project_info.NativeProjectInfo(target)
- self.assertFalse(mock_build.called)
- config.is_skip_build = False
- native_project_info.NativeProjectInfo(target)
- self.assertTrue(mock_build.called)
+ mock_get_need.return_value = ['mod1', 'mod2']
+ native_project_info.NativeProjectInfo.generate_projects([target])
+ self.assertTrue(mock_mod_info.called)
+ self.assertTrue(mock_get_need.called)
+ self.assertTrue(mock_batch.called)
+
+ def test_get_need_builds_without_needed_build(self):
+ """Test _get_need_builds method without needed build."""
+ targets = ['mod1', 'mod2']
+ native_project_info.NativeProjectInfo._init_modules_info()
+ modules_info = native_project_info.NativeProjectInfo.modules_info
+ modules_info = mock.Mock()
+ modules_info.is_module.return_value = [True, True]
+ modules_info.is_module_need_build.return_value = [False, False]
+ self.assertEqual(
+ set(),
+ native_project_info.NativeProjectInfo._get_need_builds(targets))
if __name__ == '__main__':