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__':