DO NOT MERGE - Merge build QP1A.190711.001 into stage-aosp-master history
Bug: 139893257
Change-Id: Ic2dc4f1e3e76381e58ae3345f337779f96809dce
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 29dc0af..d096465 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -3,3 +3,4 @@
[Hook Scripts]
aidegen_unittests = ${REPO_ROOT}/prebuilts/asuite/atest/linux-x86/atest aidegen_unittests --host
+run_unittests = ${REPO_ROOT}/tools/asuite/aidegen/run_tests.sh
diff --git a/aidegen/.coveragerc b/aidegen/.coveragerc
index c742664..453c2f4 100644
--- a/aidegen/.coveragerc
+++ b/aidegen/.coveragerc
@@ -9,6 +9,7 @@
# omit file patterns must be listed one per line. */.local/* /usr/*
omit =
*_unittest.py
+ *run_unittests.py
*__init__.py
*unittest_constants.py
diff --git a/aidegen/Android.bp b/aidegen/Android.bp
index 04442d3..511b681 100644
--- a/aidegen/Android.bp
+++ b/aidegen/Android.bp
@@ -72,6 +72,7 @@
],
libs: [
"py-mock",
+ "atest_module_info",
"asuite_cc_client",
],
test_config: "aidegen_unittests.xml",
diff --git a/aidegen/aidegen_main.py b/aidegen/aidegen_main.py
index 186c1e0..a7f5290 100644
--- a/aidegen/aidegen_main.py
+++ b/aidegen/aidegen_main.py
@@ -46,23 +46,17 @@
import traceback
from aidegen import constant
-from aidegen.lib.android_dev_os import AndroidDevOS
+from aidegen.lib import aidegen_metrics
+from aidegen.lib import android_dev_os
from aidegen.lib import common_util
-from aidegen.lib.common_util import COLORED_INFO
-from aidegen.lib.common_util import COLORED_PASS
-from aidegen.lib.common_util import is_android_root
-from aidegen.lib.common_util import time_logged
-from aidegen.lib.errors import AIDEgenError
-from aidegen.lib.errors import IDENotExistError
-from aidegen.lib.ide_util import IdeUtil
-from aidegen.lib.metrics import log_usage
-from aidegen.lib.metrics import starts_asuite_metrics
-from aidegen.lib.metrics import ends_asuite_metrics
-from aidegen.lib.module_info_util import generate_module_info_json
-from aidegen.lib.project_file_gen import generate_eclipse_project_files
-from aidegen.lib.project_file_gen import generate_ide_project_files
-from aidegen.lib.project_info import ProjectInfo
-from aidegen.lib.source_locator import multi_projects_locate_source
+from aidegen.lib import eclipse_project_file_gen
+from aidegen.lib import errors
+from aidegen.lib import ide_util
+from aidegen.lib import module_info
+from aidegen.lib import project_config
+from aidegen.lib import project_file_gen
+from aidegen.lib import project_info
+from aidegen.lib import source_locator
AIDEGEN_REPORT_LINK = ('To report the AIDEGen tool problem, please use this '
'link: https://goto.google.com/aidegen-bug')
@@ -73,7 +67,7 @@
or - specify "aidegen -n" to generate project file only
"""
-_CONGRATULATION = COLORED_PASS('CONGRATULATION:')
+_CONGRATULATION = common_util.COLORED_PASS('CONGRATULATION:')
_LAUNCH_SUCCESS_MSG = (
'IDE launched successfully. Please check your IDE window.')
_IDE_CACHE_REMINDER_MSG = (
@@ -81,21 +75,15 @@
'analysis, please consider to clear IDE caches if necessary. To do that, in'
' IntelliJ IDEA, go to [File > Invalidate Caches / Restart...].')
-_SKIP_BUILD_INFO = ('If you are sure the related modules and dependencies have '
- 'been already built, please try to use command {} to skip '
- 'the building process.')
_MAX_TIME = 1
_SKIP_BUILD_INFO_FUTURE = ''.join([
'AIDEGen build time exceeds {} minute(s).\n'.format(_MAX_TIME),
- _SKIP_BUILD_INFO.rstrip('.'), ' in the future.'
+ project_config.SKIP_BUILD_INFO.rstrip('.'), ' in the future.'
])
-_SKIP_BUILD_CMD = 'aidegen {} -s'
-_INFO = COLORED_INFO('INFO:')
+_INFO = common_util.COLORED_INFO('INFO:')
_SKIP_MSG = _SKIP_BUILD_INFO_FUTURE.format(
- COLORED_INFO('aidegen [ module(s) ] -s'))
+ common_util.COLORED_INFO('aidegen [ module(s) ] -s'))
_TIME_EXCEED_MSG = '\n{} {}\n'.format(_INFO, _SKIP_MSG)
-_LOG_FORMAT = '%(asctime)s %(filename)s:%(lineno)s:%(levelname)s: %(message)s'
-_DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
def _parse_args(args):
@@ -155,7 +143,7 @@
'--skip-build',
dest='skip_build',
action='store_true',
- help=('Skip building jar or modules that create java files in build '
+ help=('Skip building jars or modules that create java files in build '
'time, e.g. R/AIDL/Logtags.'))
parser.add_argument(
'-a',
@@ -166,18 +154,6 @@
return parser.parse_args(args)
-def _configure_logging(verbose):
- """Configure the logger.
-
- Args:
- verbose: A boolean. If true, display DEBUG level logs.
- """
- log_format = _LOG_FORMAT
- datefmt = _DATE_FORMAT
- level = logging.DEBUG if verbose else logging.INFO
- logging.basicConfig(level=level, format=log_format, datefmt=datefmt)
-
-
def _get_ide_util_instance(args):
"""Get an IdeUtil class instance for launching IDE.
@@ -185,79 +161,34 @@
args: A list of arguments.
Returns:
- A IdeUtil class instance.
+ An IdeUtil class instance.
"""
if args.no_launch:
return None
- ide_util_obj = IdeUtil(args.ide_installed_path, args.ide[0],
- args.config_reset,
- AndroidDevOS.MAC == AndroidDevOS.get_os_type())
+ ide_util_obj = ide_util.IdeUtil(
+ args.ide_installed_path, args.ide[0], args.config_reset,
+ (android_dev_os.AndroidDevOS.MAC ==
+ android_dev_os.AndroidDevOS.get_os_type()))
if not ide_util_obj.is_ide_installed():
ipath = args.ide_installed_path or ide_util_obj.get_default_path()
err = _NO_LAUNCH_IDE_CMD.format(ipath)
logging.error(err)
- raise IDENotExistError(err)
+ raise errors.IDENotExistError(err)
return ide_util_obj
-def _check_skip_build(args):
- """Check if users skip building target, display the warning message.
-
- Args:
- args: A list of arguments.
- """
- if not args.skip_build:
- msg = _SKIP_BUILD_INFO.format(
- COLORED_INFO(_SKIP_BUILD_CMD.format(' '.join(args.targets))))
- print('\n{} {}\n'.format(_INFO, msg))
-
-
-def _generate_project_files(ide, projects):
+def _generate_project_files(projects):
"""Generate project files by IDE type.
Args:
- ide: A character to represent IDE type.
projects: A list of ProjectInfo instances.
"""
- if ide.lower() == 'e':
- generate_eclipse_project_files(projects)
+ if project_info.ProjectInfo.config.ide_name == constant.IDE_ECLIPSE:
+ eclipse_project_file_gen.EclipseConf.generate_ide_project_files(
+ projects)
else:
- generate_ide_project_files(projects)
-
-
-def _compile_targets_for_whole_android_tree(atest_module_info, targets, cwd):
- """Compile a list of targets to include whole Android tree in the project.
-
- Adding the whole Android tree to the project will do two things,
- 1. If current working directory is not Android root, change the target to
- its relative path to root and change current working directory to root.
- If we don't change directory it's hard to deal with the whole Android
- tree together with the sub-project.
- 2. If the whole Android tree target is not in the target list, insert it to
- the first one.
-
- Args:
- atest_module_info: A instance of atest module-info object.
- targets: A list of targets to be built.
- cwd: A path of current working directory.
-
- Returns:
- A list of targets after adjustment.
- """
- new_targets = []
- if is_android_root(cwd):
- new_targets = list(targets)
- else:
- for target in targets:
- _, abs_path = common_util.get_related_paths(atest_module_info,
- target)
- rel_path = os.path.relpath(abs_path, constant.ANDROID_ROOT_PATH)
- new_targets.append(rel_path)
- os.chdir(constant.ANDROID_ROOT_PATH)
-
- if new_targets[0] != '':
- new_targets.insert(0, '')
- return new_targets
+ project_file_gen.ProjectFileGenerator.generate_ide_project_files(
+ projects)
def _launch_ide(ide_util_obj, project_absolute_path):
@@ -273,12 +204,12 @@
ide_util_obj: An ide_util instance.
project_absolute_path: A string of project absolute path.
"""
- ide_util_obj.config_ide()
- ide_util_obj.launch_ide(project_absolute_path)
+ ide_util_obj.config_ide(project_absolute_path)
+ ide_util_obj.launch_ide()
print('\n{} {}\n'.format(_CONGRATULATION, _LAUNCH_SUCCESS_MSG))
-def _check_whole_android_tree(atest_module_info, targets, android_tree):
+def _check_whole_android_tree(targets, android_tree):
"""Check if it's a building project file for the whole Android tree.
The rules:
@@ -289,25 +220,36 @@
2. If android_tree is True, add whole Android tree to the project.
Args:
- atest_module_info: A instance of atest module-info object.
- targets: A list of targets to be built.
+ targets: A list of targets to be imported.
android_tree: A boolean, True if it's a whole Android tree case,
otherwise False.
Returns:
A list of targets to be built.
"""
- cwd = os.getcwd()
- if not android_tree and is_android_root(cwd) and targets == ['']:
- android_tree = True
- new_targets = targets
+ if common_util.is_android_root(os.getcwd()) and targets == ['']:
+ return [constant.WHOLE_ANDROID_TREE_TARGET]
+ new_targets = targets.copy()
if android_tree:
- new_targets = _compile_targets_for_whole_android_tree(
- atest_module_info, targets, cwd)
+ new_targets.insert(0, constant.WHOLE_ANDROID_TREE_TARGET)
return new_targets
-@time_logged(message=_TIME_EXCEED_MSG, maximum=_MAX_TIME)
+def _is_whole_android_tree(targets, android_tree):
+ """Checks is AIDEGen going to process whole android tree.
+
+ Args:
+ targets: A list of targets to be imported.
+ android_tree: A boolean, True if it's a whole Android tree case,
+ otherwise False.
+ Returns:
+ A boolean, True when user is going to import whole Android tree.
+ """
+ return (android_tree or
+ (common_util.is_android_root(os.getcwd()) and targets == ['']))
+
+
+@common_util.time_logged(message=_TIME_EXCEED_MSG, maximum=_MAX_TIME)
def main_with_message(args):
"""Main entry with skip build message.
@@ -317,7 +259,7 @@
aidegen_main(args)
-@time_logged
+@common_util.time_logged
def main_without_message(args):
"""Main entry without skip build message.
@@ -339,8 +281,10 @@
exit_code = constant.EXIT_CODE_NORMAL
try:
args = _parse_args(argv)
- _configure_logging(args.verbose)
- starts_asuite_metrics()
+ common_util.configure_logging(args.verbose)
+ references = [constant.ANDROID_TREE] if _is_whole_android_tree(
+ args.targets, args.android_tree) else []
+ aidegen_metrics.starts_asuite_metrics(references)
if args.skip_build:
main_without_message(args)
else:
@@ -348,53 +292,69 @@
except BaseException as err:
exit_code = constant.EXIT_CODE_EXCEPTION
_, exc_value, exc_traceback = sys.exc_info()
- if isinstance(err, AIDEgenError):
+ if isinstance(err, errors.AIDEgenError):
exit_code = constant.EXIT_CODE_AIDEGEN_EXCEPTION
# Filter out sys.Exit(0) case, which is not an exception case.
if isinstance(err, SystemExit) and exc_value.code == 0:
exit_code = constant.EXIT_CODE_NORMAL
- finally:
if exit_code is not constant.EXIT_CODE_NORMAL:
error_message = str(exc_value)
traceback_list = traceback.format_tb(exc_traceback)
traceback_list.append(error_message)
traceback_str = ''.join(traceback_list)
+ aidegen_metrics.ends_asuite_metrics(exit_code, traceback_str,
+ error_message)
# print out the trackback message for developers to debug
print(traceback_str)
- ends_asuite_metrics(exit_code, traceback_str, error_message)
- else:
- ends_asuite_metrics(exit_code)
+ raise err
+ finally:
+ if exit_code is constant.EXIT_CODE_NORMAL:
+ aidegen_metrics.ends_asuite_metrics(exit_code)
+ print('\n{0} {1}\n\n{0} {2}\n'.format(_INFO, AIDEGEN_REPORT_LINK,
+ _IDE_CACHE_REMINDER_MSG))
+
+
+def _adjust_cwd_for_whole_tree_project(args, mod_info):
+ """The wrapper to handle the directory change for whole tree case.
+
+ Args:
+ args: A list of system arguments.
+ mod_info: A instance of atest module_info.
+
+ Returns:
+ A list of ProjectInfo instance
+
+ """
+ targets = _check_whole_android_tree(args.targets, args.android_tree)
+ project_info.ProjectInfo.modules_info = module_info.AidegenModuleInfo(
+ force_build=False,
+ module_file=None,
+ atest_module_info=mod_info,
+ projects=targets,
+ verbose=args.verbose,
+ skip_build=args.skip_build)
+
+ return project_info.ProjectInfo.generate_projects(targets)
def aidegen_main(args):
"""AIDEGen main entry.
- Try to generates project files for using in IDE.
+ Try to generate project files for using in IDE.
Args:
args: A list of system arguments.
"""
- log_usage()
# Pre-check for IDE relevant case, then handle dependency graph job.
ide_util_obj = _get_ide_util_instance(args)
- _check_skip_build(args)
+ project_info.ProjectInfo.config = project_config.ProjectConfig(args)
atest_module_info = common_util.get_atest_module_info(args.targets)
- targets = _check_whole_android_tree(atest_module_info, args.targets,
- args.android_tree)
- ProjectInfo.modules_info = generate_module_info_json(
- atest_module_info, targets, args.verbose, args.skip_build)
- projects = ProjectInfo.generate_projects(atest_module_info, targets)
- multi_projects_locate_source(projects, args.verbose, args.depth,
- constant.IDE_NAME_DICT[args.ide[0]],
- args.skip_build)
- _generate_project_files(args.ide[0], projects)
+ projects = _adjust_cwd_for_whole_tree_project(args, atest_module_info)
+ source_locator.multi_projects_locate_source(projects, args.verbose)
+ _generate_project_files(projects)
if ide_util_obj:
_launch_ide(ide_util_obj, projects[0].project_absolute_path)
if __name__ == '__main__':
- try:
- main(sys.argv[1:])
- finally:
- print('\n{0} {1}\n\n{0} {2}\n'.format(_INFO, AIDEGEN_REPORT_LINK,
- _IDE_CACHE_REMINDER_MSG))
+ main(sys.argv[1:])
diff --git a/aidegen/aidegen_main_unittest.py b/aidegen/aidegen_main_unittest.py
index cc10d01..32d7a21 100644
--- a/aidegen/aidegen_main_unittest.py
+++ b/aidegen/aidegen_main_unittest.py
@@ -22,16 +22,16 @@
import unittest
from unittest import mock
-import aidegen.unittest_constants as uc
from aidegen import aidegen_main
-from aidegen.lib import metrics
from aidegen import constant
+from aidegen import unittest_constants
+from aidegen.lib import aidegen_metrics
from aidegen.lib import common_util
-from aidegen.lib.common_util import COLORED_INFO
-from aidegen.lib.errors import IDENotExistError
-from aidegen.lib.errors import ProjectPathNotExistError
-from aidegen.lib.ide_util import IdeUtil
-from atest import module_info
+from aidegen.lib import eclipse_project_file_gen
+from aidegen.lib import errors
+from aidegen.lib import ide_util
+from aidegen.lib import project_file_gen
+from aidegen.lib import project_info
# pylint: disable=protected-access
@@ -58,8 +58,9 @@
self.assertEqual(args.ide[0], 's')
args = aidegen_main._parse_args(['-i', 'e'])
self.assertEqual(args.ide[0], 'e')
- args = aidegen_main._parse_args(['-p', uc.TEST_MODULE])
- self.assertEqual(args.ide_installed_path, uc.TEST_MODULE)
+ args = aidegen_main._parse_args(['-p', unittest_constants.TEST_MODULE])
+ self.assertEqual(args.ide_installed_path,
+ unittest_constants.TEST_MODULE)
args = aidegen_main._parse_args(['-n'])
self.assertEqual(args.no_launch, True)
args = aidegen_main._parse_args(['-r'])
@@ -67,98 +68,70 @@
args = aidegen_main._parse_args(['-s'])
self.assertEqual(args.skip_build, True)
- @mock.patch('aidegen_main.logging.basicConfig')
- def test_configure_logging(self, mock_log_config):
- """Test _configure_logging with different arguments."""
- aidegen_main._configure_logging(True)
- log_format = aidegen_main._LOG_FORMAT
- datefmt = aidegen_main._DATE_FORMAT
- level = aidegen_main.logging.DEBUG
- self.assertTrue(
- mock_log_config.called_with(
- level=level, format=log_format, datefmt=datefmt))
- aidegen_main._configure_logging(False)
- level = aidegen_main.logging.INFO
- self.assertTrue(
- mock_log_config.called_with(
- level=level, format=log_format, datefmt=datefmt))
-
- @mock.patch.object(IdeUtil, 'is_ide_installed')
- def test_get_ide_util_instance(self, mock_installed):
+ @mock.patch.object(ide_util.IdeIntelliJ, '_get_preferred_version')
+ @mock.patch.object(ide_util.IdeUtil, 'is_ide_installed')
+ def test_get_ide_util_instance(self, mock_installed, mock_preference):
"""Test _get_ide_util_instance with different conditions."""
target = 'tradefed'
args = aidegen_main._parse_args([target, '-n'])
self.assertEqual(aidegen_main._get_ide_util_instance(args), None)
args = aidegen_main._parse_args([target])
+ mock_preference.return_value = None
self.assertIsInstance(
- aidegen_main._get_ide_util_instance(args), IdeUtil)
+ aidegen_main._get_ide_util_instance(args), ide_util.IdeUtil)
mock_installed.return_value = False
- with self.assertRaises(IDENotExistError):
+ with self.assertRaises(errors.IDENotExistError):
aidegen_main._get_ide_util_instance(args)
- @mock.patch('builtins.print')
- def test_check_skip_build(self, mock_print):
- """Test _check_skip_build with different conditions."""
- target = 'tradefed'
- args = aidegen_main._parse_args([target, '-s'])
- aidegen_main._check_skip_build(args)
- self.assertFalse(mock_print.called)
- args = aidegen_main._parse_args([target])
- aidegen_main._check_skip_build(args)
- msg = aidegen_main._SKIP_BUILD_INFO.format(
- COLORED_INFO(
- aidegen_main._SKIP_BUILD_CMD.format(' '.join(args.targets))))
- info = '\n{} {}\n'.format(aidegen_main._INFO, msg)
- self.assertTrue(mock_print.called_with(info))
-
- @mock.patch.object(aidegen_main, 'generate_ide_project_files')
- @mock.patch.object(aidegen_main, 'generate_eclipse_project_files')
- def test_generate_project_files(self, mock_eclipse, mock_ide):
+ @mock.patch('aidegen.lib.project_config.ProjectConfig')
+ @mock.patch.object(project_file_gen.ProjectFileGenerator,
+ 'generate_ide_project_files')
+ @mock.patch.object(eclipse_project_file_gen.EclipseConf,
+ 'generate_ide_project_files')
+ def test_generate_project_files(self, mock_eclipse, mock_ide, mock_config):
"""Test _generate_project_files with different conditions."""
projects = ['module_a', 'module_v']
- aidegen_main._generate_project_files('e', projects)
+ project_info.ProjectInfo.config = mock_config
+ mock_config.ide_name = constant.IDE_ECLIPSE
+ aidegen_main._generate_project_files(projects)
self.assertTrue(mock_eclipse.called_with(projects))
- aidegen_main._generate_project_files('s', projects)
+ mock_config.ide_name = constant.IDE_ANDROID_STUDIO
+ aidegen_main._generate_project_files(projects)
self.assertTrue(mock_ide.called_with(projects))
- aidegen_main._generate_project_files('j', projects)
+ mock_config.ide_name = constant.IDE_INTELLIJ
+ aidegen_main._generate_project_files(projects)
self.assertTrue(mock_ide.called_with(projects))
@mock.patch.object(common_util, 'get_atest_module_info')
- @mock.patch.object(metrics, 'log_usage')
- def test_show_collect_data_notice(self, mock_log, mock_get):
+ @mock.patch.object(aidegen_metrics, 'starts_asuite_metrics')
+ def test_show_collect_data_notice(self, mock_metrics, mock_get):
"""Test main process always run through the target test function."""
target = 'nothing'
args = aidegen_main._parse_args([target, '-s', '-n'])
- with self.assertRaises(ProjectPathNotExistError):
+ with self.assertRaises(errors.ProjectPathNotExistError):
err = common_util.PATH_NOT_EXISTS_ERROR.format(target)
- mock_get.side_effect = ProjectPathNotExistError(err)
+ mock_get.side_effect = errors.ProjectPathNotExistError(err)
aidegen_main.main_without_message(args)
- self.assertTrue(mock_log.called)
+ self.assertTrue(mock_metrics.called)
- @mock.patch.object(common_util, 'get_related_paths')
- def test_compile_targets_for_whole_android_tree(self, mock_get):
- """Test _add_whole_android_tree_project with different conditions."""
- mod_info = module_info.ModuleInfo()
- targets = ['']
- cwd = constant.ANDROID_ROOT_PATH
- self.assertEqual(
- targets,
- aidegen_main._compile_targets_for_whole_android_tree(
- mod_info, targets, cwd))
- base_dir = 'frameworks/base'
- expected_targets = ['', base_dir]
- cwd = os.path.join(constant.ANDROID_ROOT_PATH, base_dir)
- mock_get.return_value = None, cwd
- self.assertEqual(
- expected_targets,
- aidegen_main._compile_targets_for_whole_android_tree(
- mod_info, targets, cwd))
- targets = [base_dir]
- cwd = constant.ANDROID_ROOT_PATH
- self.assertEqual(
- expected_targets,
- aidegen_main._compile_targets_for_whole_android_tree(
- mod_info, targets, cwd))
+ @mock.patch.object(os, 'getcwd')
+ def test_is_whole_android_tree(self, mock_getcwd):
+ """Test _is_whole_android_tree with different conditions."""
+ self.assertTrue(aidegen_main._is_whole_android_tree(['a'], True))
+ self.assertFalse(aidegen_main._is_whole_android_tree(['a'], False))
+ mock_getcwd.return_value = common_util.get_android_root_dir()
+ self.assertTrue(aidegen_main._is_whole_android_tree([''], False))
+ mock_getcwd.return_value = 'frameworks/base'
+ self.assertFalse(aidegen_main._is_whole_android_tree([''], False))
+
+ @mock.patch.object(aidegen_main, 'main_with_message')
+ @mock.patch.object(aidegen_main, 'main_without_message')
+ def test_main(self, mock_without, mock_with):
+ """Test main with conditions."""
+ aidegen_main.main(['-s'])
+ self.assertEqual(mock_without.call_count, 1)
+ aidegen_main.main([''])
+ self.assertEqual(mock_with.call_count, 1)
if __name__ == '__main__':
diff --git a/aidegen/constant.py b/aidegen/constant.py
index b9e7e71..65a264d 100644
--- a/aidegen/constant.py
+++ b/aidegen/constant.py
@@ -15,31 +15,33 @@
# limitations under the License.
"""The common definitions of AIDEgen"""
-import os
-
-from atest import constants
-
# Env constant
OUT_DIR_COMMON_BASE_ENV_VAR = 'OUT_DIR_COMMON_BASE'
ANDROID_DEFAULT_OUT = 'out'
-ANDROID_HOST_OUT = os.environ.get(constants.ANDROID_HOST_OUT)
-ANDROID_ROOT_PATH = os.environ.get(constants.ANDROID_BUILD_TOP)
-AIDEGEN_ROOT_PATH = os.path.join(ANDROID_ROOT_PATH, 'tools/asuite/aidegen')
-ANDROID_OUT_DIR = os.environ.get(constants.ANDROID_OUT_DIR)
-OUT_DIR_COMMON_BASE = os.getenv(OUT_DIR_COMMON_BASE_ENV_VAR)
-
-# Constants for out dir
-ANDROID_OUT_DIR_COMMON_BASE = (os.path.join(
- OUT_DIR_COMMON_BASE, os.path.basename(ANDROID_ROOT_PATH))
- if OUT_DIR_COMMON_BASE else None)
-OUT_DIR = ANDROID_OUT_DIR or ANDROID_OUT_DIR_COMMON_BASE or ANDROID_DEFAULT_OUT
-SOONG_OUT_DIR_PATH = os.path.join(ANDROID_ROOT_PATH, OUT_DIR, 'soong')
-RELATIVE_HOST_OUT = os.path.relpath(ANDROID_HOST_OUT, ANDROID_ROOT_PATH)
+AIDEGEN_ROOT_PATH = 'tools/asuite/aidegen'
# Constants for module's info.
KEY_PATH = 'path'
-KEY_DEP = 'dependencies'
+KEY_DEPENDENCIES = 'dependencies'
KEY_DEPTH = 'depth'
+KEY_CLASS = 'class'
+KEY_INSTALLED = 'installed'
+KEY_SRCS = 'srcs'
+KEY_SRCJARS = 'srcjars'
+KEY_CLASSES_JAR = 'classes_jar'
+KEY_TAG = 'tags'
+KEY_COMPATIBILITY = 'compatibility_suites'
+KEY_AUTO_TEST_CONFIG = 'auto_test_config'
+KEY_MODULE_NAME = 'module_name'
+KEY_TEST_CONFIG = 'test_config'
+# Java related classes.
+JAVA_TARGET_CLASSES = ['APPS', 'JAVA_LIBRARIES', 'ROBOLECTRIC']
+# C, C++ related classes.
+NATIVE_TARGET_CLASSES = [
+ 'HEADER_LIBRARIES', 'NATIVE_TESTS', 'STATIC_LIBRARIES', 'SHARED_LIBRARIES'
+]
+TARGET_CLASSES = JAVA_TARGET_CLASSES
+TARGET_CLASSES.extend(NATIVE_TARGET_CLASSES)
# Constants for IDE util.
IDE_ECLIPSE = 'Eclipse'
@@ -52,3 +54,47 @@
EXIT_CODE_NORMAL = 0
EXIT_CODE_AIDEGEN_EXCEPTION = 1
AIDEGEN_TOOL_NAME = 'aidegen'
+ANDROID_TREE = 'is_android_tree'
+
+# Constants for file names
+MERGED_MODULE_INFO = 'merged_module_info.json'
+BLUEPRINT_JSONFILE_NAME = 'module_bp_java_deps.json'
+
+# Constants for whole Android tree
+WHOLE_ANDROID_TREE_TARGET = '#WHOLE_ANDROID_TREE#'
+
+# Content of iml file.
+FILE_IML = """<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+@FACETS@
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+@SOURCES@
+@SRCJAR@
+ <orderEntry type="sourceFolder" forTests="false" />
+@MODULE_DEPENDENCIES@
+ <orderEntry type="inheritedJdk" />
+ </component>
+</module>
+"""
+
+# IDEA XML templates
+# The template content of modules.xml.
+MODULES_XML = """<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+@MODULES@
+@ENABLE_DEBUGGER_MODULE@
+ </modules>
+ </component>
+</project>
+"""
+# The template content of vcs.xml.
+VCS_XML = """<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+@VCS@
+ </component>
+</project>
+"""
diff --git a/aidegen/lib/aidegen_metrics.py b/aidegen/lib/aidegen_metrics.py
new file mode 100644
index 0000000..82eb6cd
--- /dev/null
+++ b/aidegen/lib/aidegen_metrics.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+# Copyright 2018 - 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.
+
+"""AIDEgen metrics functions."""
+
+import logging
+import os
+import platform
+import sys
+
+from aidegen import constant
+from atest import atest_utils
+
+try:
+ from asuite.metrics import metrics
+ from asuite.metrics import metrics_base
+ from asuite.metrics import metrics_utils
+except ImportError:
+ logging.debug('Import metrics fail, can\'t send metrics.')
+ metrics = None
+ metrics_base = None
+ metrics_utils = None
+
+
+def starts_asuite_metrics(references):
+ """Starts to record metrics data.
+
+ Send a metrics data to log server at the same time.
+
+ Args:
+ references: a list of reference data, when importing whole Android
+ it contains 'is_android_tree'.
+ """
+ if not metrics:
+ return
+ atest_utils.print_data_collection_notice()
+ metrics_base.MetricsBase.tool_name = constant.AIDEGEN_TOOL_NAME
+ metrics_utils.get_start_time()
+ command = ' '.join(sys.argv)
+ metrics.AtestStartEvent(
+ command_line=command,
+ test_references=references,
+ cwd=os.getcwd(),
+ os=platform.platform())
+
+
+def ends_asuite_metrics(exit_code, stacktrace='', logs=''):
+ """Send the end event to log server.
+
+ Args:
+ exit_code: An integer of exit code.
+ stacktrace: A string of stacktrace.
+ logs: A string of logs.
+ """
+ if not metrics_utils:
+ return
+ metrics_utils.send_exit_event(
+ exit_code,
+ stacktrace=stacktrace,
+ logs=logs)
diff --git a/aidegen/lib/aidegen_metrics_unittest.py b/aidegen/lib/aidegen_metrics_unittest.py
new file mode 100644
index 0000000..b4c037a
--- /dev/null
+++ b/aidegen/lib/aidegen_metrics_unittest.py
@@ -0,0 +1,63 @@
+#!/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.
+
+"""Unittests for aidegen_metrics."""
+
+import unittest
+from unittest import mock
+
+from aidegen import constant
+from aidegen.lib import aidegen_metrics
+from atest import atest_utils
+
+
+try:
+ from asuite.metrics import metrics
+ from asuite.metrics import metrics_utils
+except ImportError:
+ metrics = None
+ metrics_utils = None
+
+
+class AidegenMetricsUnittests(unittest.TestCase):
+ """Unit tests for aidegen_metrics.py."""
+
+ @mock.patch.object(atest_utils, 'print_data_collection_notice')
+ def test_starts_asuite_metrics(self, mock_print_data):
+ """Test starts_asuite_metrics."""
+ references = ['nothing']
+ if not metrics:
+ aidegen_metrics.starts_asuite_metrics(references)
+ self.assertFalse(mock_print_data.called)
+ else:
+ with mock.patch.object(metrics_utils, 'get_start_time') as mk_get:
+ with mock.patch.object(metrics, 'AtestStartEvent') as mk_start:
+ aidegen_metrics.starts_asuite_metrics(references)
+ self.assertTrue(mock_print_data.called)
+ self.assertTrue(mk_get.called)
+ self.assertTrue(mk_start.called)
+
+ def test_ends_asuite_metrics(self):
+ """Test ends_asuite_metrics."""
+ exit_code = constant.EXIT_CODE_NORMAL
+ if metrics_utils:
+ with mock.patch.object(metrics_utils, 'send_exit_event') as mk_send:
+ aidegen_metrics.ends_asuite_metrics(exit_code)
+ self.assertTrue(mk_send.called)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/aidegen/lib/common_util.py b/aidegen/lib/common_util.py
index 730b442..91590c5 100644
--- a/aidegen/lib/common_util.py
+++ b/aidegen/lib/common_util.py
@@ -22,36 +22,34 @@
import logging
import os
+import sys
import time
from functools import partial
from functools import wraps
from aidegen import constant
-from aidegen.lib.errors import FakeModuleError
-from aidegen.lib.errors import NoModuleDefinedInModuleInfoError
-from aidegen.lib.errors import ProjectOutsideAndroidRootError
-from aidegen.lib.errors import ProjectPathNotExistError
+from aidegen.lib import errors
+from atest import atest_utils
from atest import constants
from atest import module_info
-from atest.atest_utils import colorize
-COLORED_INFO = partial(colorize, color=constants.MAGENTA, highlight=False)
-COLORED_PASS = partial(colorize, color=constants.GREEN, highlight=False)
-COLORED_FAIL = partial(colorize, color=constants.RED, highlight=False)
+
+COLORED_INFO = partial(
+ atest_utils.colorize, color=constants.MAGENTA, highlight=False)
+COLORED_PASS = partial(
+ atest_utils.colorize, color=constants.GREEN, highlight=False)
+COLORED_FAIL = partial(
+ atest_utils.colorize, color=constants.RED, highlight=False)
FAKE_MODULE_ERROR = '{} is a fake module.'
OUTSIDE_ROOT_ERROR = '{} is outside android root.'
PATH_NOT_EXISTS_ERROR = 'The path {} doesn\'t exist.'
NO_MODULE_DEFINED_ERROR = 'No modules defined at {}.'
-# Java related classes.
-JAVA_TARGET_CLASSES = ['APPS', 'JAVA_LIBRARIES', 'ROBOLECTRIC']
-# C, C++ related classes.
-NATIVE_TARGET_CLASSES = [
- 'HEADER_LIBRARIES', 'NATIVE_TESTS', 'STATIC_LIBRARIES', 'SHARED_LIBRARIES'
-]
-TARGET_CLASSES = JAVA_TARGET_CLASSES
-TARGET_CLASSES.extend(NATIVE_TARGET_CLASSES)
_REBUILD_MODULE_INFO = '%s We should rebuild module-info.json file for it.'
+_ENVSETUP_NOT_RUN = ('Please run "source build/envsetup.sh" and "lunch" before '
+ 'running aidegen.')
+_LOG_FORMAT = '%(asctime)s %(filename)s:%(lineno)s:%(levelname)s: %(message)s'
+_DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
def time_logged(func=None, *, message='', maximum=1):
@@ -97,6 +95,11 @@
2. Module path, e.g. packages/apps/Settings
3. Relative path, e.g. ../../packages/apps/Settings
4. Current directory, e.g. . or no argument
+ 5. An empty string, which added by AIDEGen, used for generating
+ the iml files for the whole Android repo tree.
+ e.g.
+ 1. ~/aosp$ aidegen
+ 2. ~/aosp/frameworks/base$ aidegen -a
Return:
rel_path: The relative path of a module, return None if no matching
@@ -107,28 +110,32 @@
rel_path = None
abs_path = None
if target:
+ # For the case of whole Android repo tree.
+ if target == constant.WHOLE_ANDROID_TREE_TARGET:
+ rel_path = ''
+ abs_path = get_android_root_dir()
# User inputs a module name.
- if atest_module_info.is_module(target):
+ elif atest_module_info.is_module(target):
paths = atest_module_info.get_paths(target)
if paths:
rel_path = paths[0]
- abs_path = os.path.join(constant.ANDROID_ROOT_PATH, rel_path)
+ abs_path = os.path.join(get_android_root_dir(), rel_path)
# User inputs a module path or a relative path of android root folder.
- elif (atest_module_info.get_module_names(target) or os.path.isdir(
- os.path.join(constant.ANDROID_ROOT_PATH, target))):
+ elif (atest_module_info.get_module_names(target)
+ or os.path.isdir(os.path.join(get_android_root_dir(), target))):
rel_path = target.strip(os.sep)
- abs_path = os.path.join(constant.ANDROID_ROOT_PATH, rel_path)
+ abs_path = os.path.join(get_android_root_dir(), rel_path)
# User inputs a relative path of current directory.
else:
abs_path = os.path.abspath(os.path.join(os.getcwd(), target))
- rel_path = os.path.relpath(abs_path, constant.ANDROID_ROOT_PATH)
+ rel_path = os.path.relpath(abs_path, get_android_root_dir())
else:
# User doesn't input.
abs_path = os.getcwd()
if is_android_root(abs_path):
rel_path = ''
else:
- rel_path = os.path.relpath(abs_path, constant.ANDROID_ROOT_PATH)
+ rel_path = os.path.relpath(abs_path, get_android_root_dir())
return rel_path, abs_path
@@ -159,7 +166,7 @@
Returns:
True if abs_path is Android root, otherwise False.
"""
- return abs_path == constant.ANDROID_ROOT_PATH
+ return abs_path == get_android_root_dir()
def has_build_target(atest_module_info, rel_path):
@@ -245,16 +252,16 @@
if not abs_path:
err = FAKE_MODULE_ERROR.format(target)
logging.error(err)
- raise FakeModuleError(err)
- if not abs_path.startswith(constant.ANDROID_ROOT_PATH):
+ raise errors.FakeModuleError(err)
+ if not abs_path.startswith(get_android_root_dir()):
err = OUTSIDE_ROOT_ERROR.format(abs_path)
logging.error(err)
- raise ProjectOutsideAndroidRootError(err)
+ raise errors.ProjectOutsideAndroidRootError(err)
if not os.path.isdir(abs_path):
err = PATH_NOT_EXISTS_ERROR.format(rel_path)
if raise_on_lost_module:
logging.error(err)
- raise ProjectPathNotExistError(err)
+ raise errors.ProjectPathNotExistError(err)
logging.debug(_REBUILD_MODULE_INFO, err)
return False
if (not has_build_target(atest_module_info, rel_path)
@@ -262,7 +269,7 @@
err = NO_MODULE_DEFINED_ERROR.format(rel_path)
if raise_on_lost_module:
logging.error(err)
- raise NoModuleDefinedInModuleInfoError(err)
+ raise errors.NoModuleDefinedInModuleInfoError(err)
logging.debug(_REBUILD_MODULE_INFO, err)
return False
return True
@@ -272,26 +279,28 @@
"""Get absolute path from a relative path.
Args:
- rel_path: A string, a relative path to constant.ANDROID_ROOT_PATH.
+ rel_path: A string, a relative path to get_android_root_dir().
Returns:
abs_path: A string, an absolute path starts with
- constant.ANDROID_ROOT_PATH.
+ get_android_root_dir().
"""
if not rel_path:
- return constant.ANDROID_ROOT_PATH
- if rel_path.startswith(constant.ANDROID_ROOT_PATH):
+ return get_android_root_dir()
+ if rel_path.startswith(get_android_root_dir()):
return rel_path
- return os.path.join(constant.ANDROID_ROOT_PATH, rel_path)
+ return os.path.join(get_android_root_dir(), rel_path)
def is_project_path_relative_module(data, project_relative_path):
"""Determine if the given project path is relative to the module.
The rules:
- 1. If project_relative_path is empty, it's under Android root, return
+ 1. If constant.KEY_PATH not in data, we can't tell if it's a module
+ return False.
+ 2. If project_relative_path is empty, it's under Android root, return
True.
- 2. If module's path equals or starts with project_relative_path return
+ 3. If module's path equals or starts with project_relative_path return
True, otherwise return False.
Args:
@@ -302,12 +311,12 @@
True if it's the given project path is relative to the module, otherwise
False.
"""
- if 'path' not in data:
+ if constant.KEY_PATH not in data:
return False
- path = data['path'][0]
+ path = data[constant.KEY_PATH][0]
if project_relative_path == '':
return True
- if ('class' in data
+ if (constant.KEY_CLASS in data
and (path == project_relative_path
or path.startswith(project_relative_path + os.sep))):
return True
@@ -328,13 +337,16 @@
return any(src_file.endswith(x) for x in src_file_extensions)
-def get_atest_module_info(targets):
+def get_atest_module_info(targets=None):
"""Get the right version of atest ModuleInfo instance.
The rules:
- Check if the targets don't exist in ModuleInfo, we'll regain a new atest
- ModleInfo instance by setting force_build=True and call _check_modules
- again. If targets still don't exist, raise exceptions.
+ 1. If targets is None:
+ We just want to get an atest ModuleInfo instance.
+ 2. If targets isn't None:
+ Check if the targets don't exist in ModuleInfo, we'll regain a new
+ atest ModleInfo instance by setting force_build=True and call
+ _check_modules again. If targets still don't exist, raise exceptions.
Args:
targets: A list of targets to be built.
@@ -343,7 +355,132 @@
An atest ModuleInfo instance.
"""
amodule_info = module_info.ModuleInfo()
- if not _check_modules(amodule_info, targets, raise_on_lost_module=False):
+ if targets and not _check_modules(
+ amodule_info, targets, raise_on_lost_module=False):
amodule_info = module_info.ModuleInfo(force_build=True)
_check_modules(amodule_info, targets)
return amodule_info
+
+
+def read_file_content(path):
+ """Read file's content.
+
+ Args:
+ path: Path of input file.
+
+ Returns:
+ String: Content of the file.
+ """
+ with open(path) as template:
+ return template.read()
+
+
+def file_generate(path, content):
+ """Generate file from content.
+
+ Args:
+ path: Path of target file.
+ content: String content of file.
+ """
+ if not os.path.exists(os.path.dirname(path)):
+ os.makedirs(os.path.dirname(path))
+ with open(path, 'w') as target:
+ target.write(content)
+
+
+def get_blueprint_json_path():
+ """Assemble the path of blueprint json file.
+
+ Returns:
+ module_bp_java_deps.json path.
+ """
+ return os.path.join(get_soong_out_path(), constant.BLUEPRINT_JSONFILE_NAME)
+
+
+def back_to_cwd(func):
+ """Decorate a function change directory back to its original one.
+
+ Args:
+ func: a function is to be changed back to its original directory.
+
+ Returns:
+ The wrapper function.
+ """
+
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ """A wrapper function."""
+ cwd = os.getcwd()
+ try:
+ return func(*args, **kwargs)
+ finally:
+ os.chdir(cwd)
+
+ return wrapper
+
+
+def get_android_out_dir():
+ """Get out directory in different conditions.
+
+ Returns:
+ Android out directory path.
+ """
+ android_root_path = get_android_root_dir()
+ android_out_dir = os.getenv(constants.ANDROID_OUT_DIR)
+ out_dir_common_base = os.getenv(constant.OUT_DIR_COMMON_BASE_ENV_VAR)
+ android_out_dir_common_base = (os.path.join(
+ out_dir_common_base, os.path.basename(android_root_path))
+ if out_dir_common_base else None)
+ return (android_out_dir or android_out_dir_common_base
+ or constant.ANDROID_DEFAULT_OUT)
+
+
+def get_android_root_dir():
+ """Get Android root directory.
+
+ If the path doesn't exist show message to ask users to run envsetup.sh and
+ lunch first.
+
+ Returns:
+ Android root directory path.
+ """
+ android_root_path = os.environ.get(constants.ANDROID_BUILD_TOP)
+ if not android_root_path:
+ _show_env_setup_msg_and_exit()
+ return android_root_path
+
+
+def get_aidegen_root_dir():
+ """Get AIDEGen root directory.
+
+ Returns:
+ AIDEGen root directory path.
+ """
+ return os.path.join(get_android_root_dir(), constant.AIDEGEN_ROOT_PATH)
+
+
+def _show_env_setup_msg_and_exit():
+ """Show message if users didn't run envsetup.sh and lunch."""
+ print(_ENVSETUP_NOT_RUN)
+ sys.exit(0)
+
+
+def get_soong_out_path():
+ """Assemble out directory's soong path.
+
+ Returns:
+ Out directory's soong path.
+ """
+ return os.path.join(get_android_root_dir(), get_android_out_dir(), 'soong')
+
+
+def configure_logging(verbose):
+ """Configure the logger.
+
+ Args:
+ verbose: A boolean. If true, display DEBUG level logs.
+ """
+ log_format = _LOG_FORMAT
+ datefmt = _DATE_FORMAT
+ level = logging.DEBUG if verbose else logging.INFO
+ logging.basicConfig(level=level, format=log_format, datefmt=datefmt)
diff --git a/aidegen/lib/common_util_unittest.py b/aidegen/lib/common_util_unittest.py
index bea77a1..cbd3f82 100644
--- a/aidegen/lib/common_util_unittest.py
+++ b/aidegen/lib/common_util_unittest.py
@@ -16,92 +16,105 @@
"""Unittests for common_util."""
+import logging
import os
import unittest
from unittest import mock
-from aidegen.lib.errors import FakeModuleError
-from aidegen.lib.errors import NoModuleDefinedInModuleInfoError
-from aidegen.lib.errors import ProjectOutsideAndroidRootError
-from aidegen.lib.errors import ProjectPathNotExistError
-import aidegen.unittest_constants as uc
from aidegen import constant
+from aidegen import unittest_constants
from aidegen.lib import common_util
+from aidegen.lib import errors
from atest import module_info
-#pylint: disable=protected-access
-#pylint: disable=invalid-name
+# pylint: disable=protected-access
+# pylint: disable=invalid-name
class AidegenCommonUtilUnittests(unittest.TestCase):
"""Unit tests for common_util.py"""
+ @mock.patch.object(common_util, 'get_android_root_dir')
@mock.patch.object(module_info.ModuleInfo, 'get_module_names')
@mock.patch.object(module_info.ModuleInfo, 'get_paths')
@mock.patch.object(module_info.ModuleInfo, 'is_module')
- def test_get_related_paths(self, mock_is_mod, mock_get, mock_names):
+ def test_get_related_paths(self, mock_is_mod, mock_get, mock_names,
+ mock_get_root):
"""Test get_related_paths with different conditions."""
mock_is_mod.return_value = True
mock_get.return_value = []
mod_info = module_info.ModuleInfo()
self.assertEqual((None, None),
- common_util.get_related_paths(mod_info,
- uc.TEST_MODULE))
- constant.ANDROID_ROOT_PATH = uc.TEST_PATH
- mock_get.return_value = [uc.TEST_MODULE]
- expected = (uc.TEST_MODULE, os.path.join(uc.TEST_PATH, uc.TEST_MODULE))
+ common_util.get_related_paths(
+ mod_info, unittest_constants.TEST_MODULE))
+ mock_get_root.return_value = unittest_constants.TEST_PATH
+ mock_get.return_value = [unittest_constants.TEST_MODULE]
+ expected = (unittest_constants.TEST_MODULE, os.path.join(
+ unittest_constants.TEST_PATH, unittest_constants.TEST_MODULE))
self.assertEqual(
- expected, common_util.get_related_paths(mod_info, uc.TEST_MODULE))
+ expected, common_util.get_related_paths(
+ mod_info, unittest_constants.TEST_MODULE))
mock_is_mod.return_value = False
mock_names.return_value = True
- self.assertEqual(
- expected, common_util.get_related_paths(mod_info, uc.TEST_MODULE))
+ self.assertEqual(expected, common_util.get_related_paths(
+ mod_info, unittest_constants.TEST_MODULE))
+ self.assertEqual(('', unittest_constants.TEST_PATH),
+ common_util.get_related_paths(
+ mod_info, constant.WHOLE_ANDROID_TREE_TARGET))
+ @mock.patch.object(common_util, 'get_android_root_dir')
@mock.patch.object(common_util, 'get_related_paths')
- def test_is_target_android_root(self, mock_get):
+ def test_is_target_android_root(self, mock_get_rel, mock_get_root):
"""Test is_target_android_root with different conditions."""
- mock_get.return_value = None, uc.TEST_PATH
+ mock_get_rel.return_value = None, unittest_constants.TEST_PATH
+ mock_get_root.return_value = unittest_constants.TEST_PATH
self.assertTrue(
- common_util.is_target_android_root(module_info.ModuleInfo(),
- [uc.TEST_MODULE]))
- mock_get.return_value = None, ''
+ common_util.is_target_android_root(
+ module_info.ModuleInfo(), [unittest_constants.TEST_MODULE]))
+ mock_get_rel.return_value = None, ''
self.assertFalse(
- common_util.is_target_android_root(module_info.ModuleInfo(),
- [uc.TEST_MODULE]))
+ common_util.is_target_android_root(
+ module_info.ModuleInfo(), [unittest_constants.TEST_MODULE]))
+ @mock.patch.object(common_util, 'get_android_root_dir')
@mock.patch.object(common_util, 'has_build_target')
@mock.patch('os.path.isdir')
@mock.patch.object(common_util, 'get_related_paths')
- def test_check_module(self, mock_get, mock_isdir, mock_has_target):
+ def test_check_module(self, mock_get, mock_isdir, mock_has_target,
+ mock_get_root):
"""Test if _check_module raises errors with different conditions."""
mod_info = module_info.ModuleInfo()
mock_get.return_value = None, None
- with self.assertRaises(FakeModuleError) as ctx:
- common_util._check_module(mod_info, uc.TEST_MODULE)
- expected = common_util.FAKE_MODULE_ERROR.format(uc.TEST_MODULE)
+ with self.assertRaises(errors.FakeModuleError) as ctx:
+ common_util._check_module(mod_info, unittest_constants.TEST_MODULE)
+ expected = common_util.FAKE_MODULE_ERROR.format(
+ unittest_constants.TEST_MODULE)
self.assertEqual(expected, str(ctx.exception))
- constant.ANDROID_ROOT_PATH = uc.TEST_PATH
- mock_get.return_value = None, uc.TEST_MODULE
- with self.assertRaises(ProjectOutsideAndroidRootError) as ctx:
- common_util._check_module(mod_info, uc.TEST_MODULE)
- expected = common_util.OUTSIDE_ROOT_ERROR.format(uc.TEST_MODULE)
+ mock_get_root.return_value = unittest_constants.TEST_PATH
+ mock_get.return_value = None, unittest_constants.TEST_MODULE
+ with self.assertRaises(errors.ProjectOutsideAndroidRootError) as ctx:
+ common_util._check_module(mod_info, unittest_constants.TEST_MODULE)
+ expected = common_util.OUTSIDE_ROOT_ERROR.format(
+ unittest_constants.TEST_MODULE)
self.assertEqual(expected, str(ctx.exception))
- mock_get.return_value = None, uc.TEST_PATH
+ mock_get.return_value = None, unittest_constants.TEST_PATH
mock_isdir.return_value = False
- with self.assertRaises(ProjectPathNotExistError) as ctx:
- common_util._check_module(mod_info, uc.TEST_MODULE)
- expected = common_util.PATH_NOT_EXISTS_ERROR.format(uc.TEST_MODULE)
+ with self.assertRaises(errors.ProjectPathNotExistError) as ctx:
+ common_util._check_module(mod_info, unittest_constants.TEST_MODULE)
+ expected = common_util.PATH_NOT_EXISTS_ERROR.format(
+ unittest_constants.TEST_MODULE)
self.assertEqual(expected, str(ctx.exception))
mock_isdir.return_value = True
mock_has_target.return_value = False
- mock_get.return_value = None, os.path.join(uc.TEST_PATH, 'test.jar')
- with self.assertRaises(NoModuleDefinedInModuleInfoError) as ctx:
- common_util._check_module(mod_info, uc.TEST_MODULE)
+ mock_get.return_value = None, os.path.join(unittest_constants.TEST_PATH,
+ 'test.jar')
+ with self.assertRaises(errors.NoModuleDefinedInModuleInfoError) as ctx:
+ common_util._check_module(mod_info, unittest_constants.TEST_MODULE)
expected = common_util.NO_MODULE_DEFINED_ERROR.format(
- uc.TEST_MODULE)
+ unittest_constants.TEST_MODULE)
self.assertEqual(expected, str(ctx.exception))
self.assertEqual(common_util._check_module(mod_info, '', False), False)
- self.assertEqual(common_util._check_module(mod_info, 'nothing', False),
- False)
+ self.assertEqual(
+ common_util._check_module(mod_info, 'nothing', False), False)
@mock.patch.object(common_util, '_check_module')
def test_check_modules(self, mock_check):
@@ -113,14 +126,16 @@
self.assertEqual(mock_check.call_count, 2)
target = 'nothing'
mock_check.return_value = False
- self.assertEqual(common_util._check_modules(mod_info, [target], False),
- False)
+ self.assertEqual(
+ common_util._check_modules(mod_info, [target], False), False)
- def test_get_abs_path(self):
+ @mock.patch.object(common_util, 'get_android_root_dir')
+ def test_get_abs_path(self, mock_get_root):
"""Test get_abs_path handling."""
- constant.ANDROID_ROOT_PATH = uc.TEST_DATA_PATH
- self.assertEqual(uc.TEST_DATA_PATH, common_util.get_abs_path(''))
- test_path = os.path.join(uc.TEST_DATA_PATH, 'test.jar')
+ mock_get_root.return_value = unittest_constants.TEST_DATA_PATH
+ self.assertEqual(unittest_constants.TEST_DATA_PATH,
+ common_util.get_abs_path(''))
+ test_path = os.path.join(unittest_constants.TEST_DATA_PATH, 'test.jar')
self.assertEqual(test_path, common_util.get_abs_path(test_path))
self.assertEqual(test_path, common_util.get_abs_path('test.jar'))
@@ -136,6 +151,81 @@
common_util.is_target('packages/apps/tests/test.jar',
['.so', '.a']), False)
+ @mock.patch.object(logging, 'basicConfig')
+ def test_configure_logging(self, mock_log_config):
+ """Test configure_logging with different arguments."""
+ common_util.configure_logging(True)
+ log_format = common_util._LOG_FORMAT
+ datefmt = common_util._DATE_FORMAT
+ level = common_util.logging.DEBUG
+ self.assertTrue(
+ mock_log_config.called_with(
+ level=level, format=log_format, datefmt=datefmt))
+ common_util.configure_logging(False)
+ level = common_util.logging.INFO
+ self.assertTrue(
+ mock_log_config.called_with(
+ level=level, format=log_format, datefmt=datefmt))
+
+ def test_is_project_path_relative_module(self):
+ """Test is_project_path_relative_module handling."""
+ data = {'class':['APPS']}
+ self.assertFalse(common_util.is_project_path_relative_module(data, ''))
+ data = {'class':['APPS'], 'path':['path_to_a']}
+ self.assertTrue(common_util.is_project_path_relative_module(data, ''))
+ self.assertFalse(
+ common_util.is_project_path_relative_module(data, 'test'))
+ data = {'path':['path_to_a']}
+ self.assertFalse(
+ common_util.is_project_path_relative_module(data, 'test'))
+ self.assertFalse(
+ common_util.is_project_path_relative_module(data, 'path_to_a'))
+ data = {'class':['APPS'], 'path':['test/path_to_a']}
+ self.assertTrue(
+ common_util.is_project_path_relative_module(data, 'test'))
+ self.assertFalse(
+ common_util.is_project_path_relative_module(data, 'tes'))
+
+ @mock.patch.object(common_util, '_check_modules')
+ @mock.patch.object(module_info, 'ModuleInfo')
+ def test_get_atest_module_info(self, mock_modinfo, mock_check_modules):
+ """Test get_atest_module_info handling."""
+ common_util.get_atest_module_info()
+ self.assertEqual(mock_modinfo.call_count, 1)
+ mock_modinfo.reset_mock()
+ mock_check_modules.return_value = False
+ common_util.get_atest_module_info(['nothing'])
+ self.assertEqual(mock_modinfo.call_count, 2)
+
+ @mock.patch('builtins.open', create=True)
+ def test_read_file_content(self, mock_open):
+ """Test read_file_content handling."""
+ expacted_data1 = 'Data1'
+ fileA = 'fileA'
+ mock_open.side_effect = [
+ mock.mock_open(read_data=expacted_data1).return_value
+ ]
+ self.assertEqual(expacted_data1, common_util.read_file_content(fileA))
+ mock_open.assert_called_once_with(fileA)
+
+ @mock.patch('os.getenv')
+ @mock.patch.object(common_util, 'get_android_root_dir')
+ def test_get_android_out_dir(self, mock_get_android_root_dir, mock_getenv):
+ """Test get_android_out_dir handling."""
+ root = 'my/path-to-root/master'
+ default_root = 'out'
+ android_out_root = 'eng_out'
+ mock_get_android_root_dir.return_value = root
+ mock_getenv.side_effect = ['', '']
+ self.assertEqual(default_root, common_util.get_android_out_dir())
+ mock_getenv.side_effect = [android_out_root, '']
+ self.assertEqual(android_out_root, common_util.get_android_out_dir())
+ mock_getenv.side_effect = ['', default_root]
+ self.assertEqual(os.path.join(default_root, os.path.basename(root)),
+ common_util.get_android_out_dir())
+ mock_getenv.side_effect = [android_out_root, default_root]
+ self.assertEqual(android_out_root, common_util.get_android_out_dir())
+
if __name__ == '__main__':
unittest.main()
diff --git a/aidegen/lib/config.py b/aidegen/lib/config.py
index da1ca7b..d170f3c 100644
--- a/aidegen/lib/config.py
+++ b/aidegen/lib/config.py
@@ -21,16 +21,40 @@
import logging
import os
-_DEFAULT_CONFIG_FILE = 'aidegen.config'
+from aidegen.lib import common_util
class AidegenConfig():
- """Class manages AIDEGen's configurations."""
+ """Class manages AIDEGen's configurations.
+
+ Attributes:
+ _config: A dict contains the aidegen config.
+ _config_backup: A dict contains the aidegen config.
+ """
+
+ # Constants of AIDEGen config
+ _DEFAULT_CONFIG_FILE = 'aidegen.config'
+ _CONFIG_DIR = os.path.join(
+ os.path.expanduser('~'), '.config', 'asuite', 'aidegen')
+ _CONFIG_FILE_PATH = os.path.join(_CONFIG_DIR, _DEFAULT_CONFIG_FILE)
+
+ # Constants of enable debugger
+ _ENABLE_DEBUG_CONFIG_DIR = 'enable_debugger'
+ _ENABLE_DEBUG_CONFIG_FILE = 'enable_debugger.iml'
+ _ENABLE_DEBUG_TEMPLATE_FILE = os.path.join(
+ common_util.get_aidegen_root_dir(), 'templates',
+ _ENABLE_DEBUG_CONFIG_DIR, _ENABLE_DEBUG_CONFIG_FILE)
+ _ENABLE_DEBUG_DIR = os.path.join(_CONFIG_DIR, _ENABLE_DEBUG_CONFIG_DIR)
+ _ANDROID_MANIFEST_FILE_NAME = 'AndroidManifest.xml'
+ _DIR_SRC = 'src'
+ _DIR_GEN = 'gen'
+ DEBUG_ENABLED_FILE_PATH = os.path.join(_ENABLE_DEBUG_DIR,
+ _ENABLE_DEBUG_CONFIG_FILE)
def __init__(self):
self._config = {}
self._config_backup = {}
- self._config_file = self._get_default_config_file()
+ self._create_config_folder()
def __enter__(self):
self._load_aidegen_config()
@@ -61,13 +85,13 @@
def _load_aidegen_config(self):
"""Load data from configuration file."""
- if os.path.exists(self._config_file):
+ if os.path.exists(self._CONFIG_FILE_PATH):
try:
- with open(self._config_file, 'r') as cfg_file:
+ with open(self._CONFIG_FILE_PATH, 'r') as cfg_file:
self._config = json.load(cfg_file)
except ValueError as err:
info = '{} format is incorrect, error: {}'.format(
- self._config_file, err)
+ self._CONFIG_FILE_PATH, err)
logging.info(info)
except IOError as err:
logging.error(err)
@@ -76,7 +100,7 @@
def _save_aidegen_config(self):
"""Save data to configuration file."""
if self._is_config_modified():
- with open(self._config_file, 'w') as cfg_file:
+ with open(self._CONFIG_FILE_PATH, 'w') as cfg_file:
json.dump(self._config, cfg_file, indent=4)
def _is_config_modified(self):
@@ -84,11 +108,61 @@
return any(key for key in self._config if not key in self._config_backup
or self._config[key] != self._config_backup[key])
- @staticmethod
- def _get_default_config_file():
- """Return path of default configuration file."""
- cfg_path = os.path.join(
- os.path.expanduser('~'), '.config', 'asuite', 'aidegen')
- if not os.path.exists(cfg_path):
- os.makedirs(cfg_path)
- return os.path.join(cfg_path, _DEFAULT_CONFIG_FILE)
+ def _create_config_folder(self):
+ """Create the config folder if it doesn't exist."""
+ if not os.path.exists(self._CONFIG_DIR):
+ os.makedirs(self._CONFIG_DIR)
+
+ def _gen_enable_debug_sub_dir(self, dir_name):
+ """Generate a dir under enable debug dir.
+
+ Args:
+ dir_name: A string of the folder name.
+ """
+ _dir = os.path.join(self._ENABLE_DEBUG_DIR, dir_name)
+ if not os.path.exists(_dir):
+ os.makedirs(_dir)
+
+ def _gen_empty_androidmanifest(self):
+ """Generate an empty AndroidManifest.xml under enable debug dir."""
+ _file = os.path.join(self._ENABLE_DEBUG_DIR,
+ self._ANDROID_MANIFEST_FILE_NAME)
+ if not os.path.exists(_file):
+ common_util.file_generate(_file, '')
+
+ def _gen_enable_debugger_config(self, api_level):
+ """Generate the enable_debugger.iml config file.
+
+ Create the enable_debugger.iml if it doesn't exist.
+
+ Args:
+ api_level: An integer of API level.
+ """
+ if not os.path.exists(self.DEBUG_ENABLED_FILE_PATH):
+ content = common_util.read_file_content(
+ self._ENABLE_DEBUG_TEMPLATE_FILE).format(API_LEVEL=api_level)
+ common_util.file_generate(self.DEBUG_ENABLED_FILE_PATH, content)
+
+ def create_enable_debugger_module(self, api_level):
+ """Create the enable_debugger module.
+
+ 1. Create two empty folders named src and gen.
+ 2. Create an empty file named AndroidManifest.xml
+ 3. Create the enable_denugger.iml.
+
+ Args:
+ api_level: An integer of API level.
+
+ Returns: True if successfully generate the enable debugger module,
+ otherwise False.
+ """
+ try:
+ self._gen_enable_debug_sub_dir(self._DIR_SRC)
+ self._gen_enable_debug_sub_dir(self._DIR_GEN)
+ self._gen_empty_androidmanifest()
+ self._gen_enable_debugger_config(api_level)
+ return True
+ except (IOError, OSError) as err:
+ logging.warning(('Can\'t create the enable_debugger module in %s.\n'
+ '%s'), self._CONFIG_DIR, err)
+ return False
diff --git a/aidegen/lib/config_unittest.py b/aidegen/lib/config_unittest.py
new file mode 100644
index 0000000..a31ad78
--- /dev/null
+++ b/aidegen/lib/config_unittest.py
@@ -0,0 +1,132 @@
+#!/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.
+
+"""Unittests for AidegenConfig class."""
+
+import unittest
+from unittest import mock
+
+from aidegen.lib import config
+from aidegen.lib import common_util
+
+
+# pylint: disable=protected-access
+class AidegenConfigUnittests(unittest.TestCase):
+ """Unit tests for config.py"""
+
+ @mock.patch('logging.info')
+ @mock.patch('logging.error')
+ @mock.patch('builtins.open')
+ @mock.patch('os.path.exists')
+ def test_load_aidegen_config(self, mock_exists, mock_open, mock_error,
+ mock_info):
+ """Test _load_aidegen_config."""
+ mock_exists.return_value = True
+ cfg = config.AidegenConfig()
+ mock_open.side_effect = IOError()
+ with self.assertRaises(IOError):
+ cfg._load_aidegen_config()
+ self.assertTrue(mock_error.called)
+ self.assertFalse(mock_info.called)
+ mock_open.reset()
+ mock_open.side_effect = ValueError()
+ cfg._load_aidegen_config()
+ self.assertTrue(mock_info.called)
+
+ @mock.patch('json.dump')
+ @mock.patch('builtins.open')
+ @mock.patch.object(config.AidegenConfig, '_is_config_modified')
+ def test_save_aidegen_config(self, mock_is_modified, mock_open, mock_dump):
+ """Test _save_aidegen_config."""
+ mock_is_modified.return_value = False
+ cfg = config.AidegenConfig()
+ cfg._save_aidegen_config()
+ self.assertFalse(mock_open.called)
+ self.assertFalse(mock_dump.called)
+ mock_is_modified.return_value = True
+ cfg._save_aidegen_config()
+ self.assertTrue(mock_open.called)
+ self.assertTrue(mock_dump.called)
+
+ @mock.patch('logging.warning')
+ @mock.patch.object(config.AidegenConfig, '_gen_enable_debugger_config')
+ @mock.patch.object(config.AidegenConfig, '_gen_empty_androidmanifest')
+ @mock.patch.object(config.AidegenConfig, '_gen_enable_debug_sub_dir')
+ def test_create_enable_debugger(self, mock_debug, mock_empty, mock_enable,
+ mock_warning):
+ """Test create_enable_debugger_module."""
+ cfg = config.AidegenConfig()
+ mock_debug.side_effect = IOError()
+ self.assertFalse(cfg.create_enable_debugger_module(0))
+ self.assertTrue(mock_warning.called)
+ mock_debug.side_effect = OSError()
+ self.assertFalse(cfg.create_enable_debugger_module(0))
+ self.assertTrue(mock_warning.called)
+ mock_empty.side_effect = IOError()
+ self.assertFalse(cfg.create_enable_debugger_module(0))
+ self.assertTrue(mock_warning.called)
+ mock_empty.side_effect = OSError()
+ self.assertFalse(cfg.create_enable_debugger_module(0))
+ self.assertTrue(mock_warning.called)
+ mock_enable.side_effect = IOError()
+ self.assertFalse(cfg.create_enable_debugger_module(0))
+ self.assertTrue(mock_warning.called)
+ mock_enable.side_effect = OSError()
+ self.assertFalse(cfg.create_enable_debugger_module(0))
+ self.assertTrue(mock_warning.called)
+
+ @mock.patch.object(common_util, 'file_generate')
+ @mock.patch.object(common_util, 'read_file_content')
+ @mock.patch('os.path.exists')
+ def test_gen_enable_debugger_config(self, mock_exists, mock_read, mock_gen):
+ """Test _gen_enable_debugger_config."""
+ cfg = config.AidegenConfig()
+ mock_exists.return_value = True
+ cfg._gen_enable_debugger_config(0)
+ self.assertFalse(mock_read.called)
+ self.assertFalse(mock_gen.called)
+ mock_exists.return_value = False
+ cfg._gen_enable_debugger_config(0)
+ self.assertTrue(mock_read.called)
+ self.assertTrue(mock_gen.called)
+
+ @mock.patch.object(common_util, 'file_generate')
+ @mock.patch('os.path.exists')
+ def test_gen_empty_androidmanifest(self, mock_exists, mock_gen):
+ """Test _gen_empty_androidmanifest."""
+ cfg = config.AidegenConfig()
+ mock_exists.return_value = True
+ cfg._gen_empty_androidmanifest()
+ self.assertFalse(mock_gen.called)
+ mock_exists.return_value = False
+ cfg._gen_empty_androidmanifest()
+ self.assertTrue(mock_gen.called)
+
+ @mock.patch('os.makedirs')
+ @mock.patch('os.path.exists')
+ def test_create_config_folder(self, mock_exists, mock_makedirs):
+ """Test _create_config_folder."""
+ cfg = config.AidegenConfig()
+ mock_exists.return_value = True
+ cfg._create_config_folder()
+ self.assertFalse(mock_makedirs.called)
+ mock_exists.return_value = False
+ cfg._create_config_folder()
+ self.assertTrue(mock_makedirs.called)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/aidegen/lib/eclipse_project_file_gen.py b/aidegen/lib/eclipse_project_file_gen.py
new file mode 100644
index 0000000..cc5052e
--- /dev/null
+++ b/aidegen/lib/eclipse_project_file_gen.py
@@ -0,0 +1,235 @@
+#!/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.
+
+"""It is an AIDEGen sub task: generate the .project file for Eclipse."""
+
+import os
+
+from aidegen import constant
+from aidegen.lib import common_util
+from aidegen.lib import project_file_gen
+
+
+class EclipseConf(project_file_gen.ProjectFileGenerator):
+ """Class to generate project file under the module path for Eclipse.
+
+ Attributes:
+ module_abspath: The absolute path of the target project.
+ module_relpath: The relative path of the target project.
+ module_name: The name of the target project.
+ jar_module_paths: A dict records a mapping of jar file and module path.
+ r_java_paths: A list contains the relative folder paths of the R.java
+ files.
+ project_file: The absolutely path of .project file.
+ template_project_content: A string of a template project_file content.
+ project_content: A string ready to be written into project_file.
+ src_paths: A list contains the project's source paths.
+ classpath_file: The absolutely path of .classpath file.
+ classpath_content: A string ready to be written into classpath_file.
+ """
+ # Constants of .project file
+ _TEMPLATE_PROJECT_FILE = os.path.join(common_util.get_aidegen_root_dir(),
+ 'templates/eclipse/project.xml')
+ _PROJECT_LINK = (' <link><name>{}</name><type>2</type>'
+ '<location>{}</location></link>\n')
+ _PROJECT_FILENAME = '.project'
+
+ # constans of .classpath file
+ _TEMPLATE_CLASSPATH_FILE = os.path.join(common_util.get_aidegen_root_dir(),
+ 'templates/eclipse/classpath.xml')
+ _CLASSPATH_SRC_ENTRY = (' <classpathentry kind="src" path="{}"/>\n')
+ _CLASSPATH_LIB_ENTRY = (' <classpathentry exported="true" kind="lib" '
+ 'path="{}" sourcepath="{}"/>\n')
+ _CLASSPATH_FILENAME = '.classpath'
+
+ def __init__(self, project):
+ """Initialize class.
+
+ Args:
+ project: A ProjectInfo instance.
+ """
+ super().__init__(project)
+ self.module_abspath = project.project_absolute_path
+ self.module_relpath = project.project_relative_path
+ self.module_name = project.module_name
+ self.jar_module_paths = project.source_path['jar_module_path']
+ self.r_java_paths = list(project.source_path['r_java_path'])
+ # Related value for generating .project.
+ self.project_file = os.path.join(self.module_abspath,
+ self._PROJECT_FILENAME)
+ self.template_project_content = common_util.read_file_content(
+ self._TEMPLATE_PROJECT_FILE)
+ self.project_content = ''
+ # Related value for generating .classpath.
+ self.src_paths = list(project.source_path['source_folder_path'])
+ self.src_paths.extend(project.source_path['test_folder_path'])
+ self.classpath_file = os.path.join(self.module_abspath,
+ self._CLASSPATH_FILENAME)
+ self.classpath_content = common_util.read_file_content(
+ self._TEMPLATE_CLASSPATH_FILE)
+
+ def _gen_r_link(self):
+ """Generate the link resources of the R paths.
+
+ E.g.
+ <link>
+ <name>dependencies/out/target/common/R</name>
+ <type>2</type>
+ <location>{ANDROID_ROOT_PATH}/out/target/common/R</location>
+ </link>
+
+ Returns: A set contains R paths link resources strings.
+ """
+ return {self._gen_link(r_path) for r_path in self.r_java_paths}
+
+ def _gen_src_links(self, relpaths):
+ """Generate the link resources from relpaths.
+
+ Args:
+ relpaths: A list of module paths which are relative to
+ ANDROID_BUILD_TOP.
+ e.g. ['relpath/to/module1', 'relpath/to/module2', ...]
+
+ Returns: A set includes all unique link resources.
+ """
+ return {self._gen_link(relpath) for relpath in relpaths}
+
+ @classmethod
+ def _gen_link(cls, relpath):
+ """Generate a link resource from a relative path.
+
+ E.g.
+ <link>
+ <name>dependencies/path/to/relpath</name>
+ <type>2</type>
+ <location>/absolute/path/to/relpath</location>
+ </link>
+
+ Args:
+ relpath: A string of a relative path to Android_BUILD_TOP.
+
+ Returns: A string of link resource.
+ """
+ alias_name = os.path.join(constant.KEY_DEPENDENCIES, relpath)
+ abs_path = os.path.join(common_util.get_android_root_dir(), relpath)
+ return cls._PROJECT_LINK.format(alias_name, abs_path)
+
+ def _create_project_content(self):
+ """Create the project file .project under the module."""
+ # links is a set to save unique link resources.
+ links = self._gen_src_links(self.jar_module_paths.values())
+ links.update(self._gen_r_link())
+ self.project_content = self.template_project_content.format(
+ PROJECTNAME=self.module_name,
+ LINKEDRESOURCES=''.join(sorted(list(links))))
+
+ def _gen_r_path_entries(self):
+ """Generate the class path entries for the R paths.
+
+ E.g.
+ <classpathentry kind="src"
+ path="dependencies/out/target/common/R"/>
+ <classpathentry kind="src"
+ path="dependencies/out/soong/.intermediates/packages/apps/
+ Settings/Settings/android_common/gen/aapt2/R"/>
+
+ Returns: A list of the R path's class path entry.
+ """
+ r_entry_list = []
+ for r_path in self.r_java_paths:
+ alias_path = os.path.join(constant.KEY_DEPENDENCIES, r_path)
+ r_entry_list.append(self._CLASSPATH_SRC_ENTRY.format(alias_path))
+ return r_entry_list
+
+ def _gen_src_path_entries(self):
+ """Generate the class path entries from srcs.
+
+ E.g.
+ The source folder paths list:
+ ['packages/apps/Settings/src',
+ 'packages/apps/Settings/tests/robotests/src',
+ 'packages/apps/Settings/tests/uitests/src',
+ 'packages/apps/Settings/tests/unit/src'
+ ]
+ It will generate the related <classpathentry> list:
+ ['<classpathentry kind="src" path="src"/>',
+ '<classpathentry kind="src" path="tests/robotests/src"/>',
+ '<classpathentry kind="src" path="tests/uitests/src"/>',
+ '<classpathentry kind="src" path="tests/unit/src"/>'
+ ]
+
+ Returns: A list of source folders' class path entries.
+ """
+ src_path_entries = []
+ for src in self.src_paths:
+ src = src.replace(self.module_relpath, '').strip(os.sep)
+ src_path_entries.append(self._CLASSPATH_SRC_ENTRY.format(src))
+ return src_path_entries
+
+ def _gen_jar_path_entries(self):
+ """Generate the jar files' class path entries.
+
+ The self.jar_module_paths is a dictionary.
+ e.g.
+ {'/abspath/to/the/file.jar': 'relpath/to/the/module'}
+ This method will generate the <classpathentry> for each jar file.
+ The format of <classpathentry> looks like:
+ <classpathentry exported="true" kind="lib"
+ path="/abspath/to/the/file.jar"
+ sourcepath="dependencies/relpath/to/the/module"/>
+
+ Returns: A list of jar files' class path entries.
+ """
+ jar_entries = []
+ for jar_relpath, module_relpath in self.jar_module_paths.items():
+ jar_abspath = os.path.join(common_util.get_android_root_dir(),
+ jar_relpath)
+ alias_module_path = os.path.join(constant.KEY_DEPENDENCIES,
+ module_relpath)
+ jar_entries.append(self._CLASSPATH_LIB_ENTRY.format(
+ jar_abspath, alias_module_path))
+ return jar_entries
+
+ def _create_classpath_content(self):
+ """Create the project file .classpath under the module."""
+ src_entries = self._gen_src_path_entries()
+ src_entries.extend(self._gen_r_path_entries())
+ jar_entries = self._gen_jar_path_entries()
+ self.classpath_content = self.classpath_content.format(
+ SRC=''.join(sorted(src_entries)),
+ LIB=''.join(sorted(jar_entries)))
+
+ def generate_project_file(self):
+ """Generate .project file of the target module."""
+ self._create_project_content()
+ common_util.file_generate(self.project_file, self.project_content)
+
+ def generate_classpath_file(self):
+ """Generate .classpath file of the target module."""
+ self._create_classpath_content()
+ common_util.file_generate(self.classpath_file, self.classpath_content)
+
+ @classmethod
+ def generate_ide_project_files(cls, projects):
+ """Generate Eclipse project files by a list of ProjectInfo instances.
+
+ Args:
+ projects: A list of ProjectInfo instances.
+ """
+ for project in projects:
+ eclipse_configure = EclipseConf(project)
+ eclipse_configure.generate_project_file()
+ eclipse_configure.generate_classpath_file()
diff --git a/aidegen/lib/eclipse_project_file_gen_unittest.py b/aidegen/lib/eclipse_project_file_gen_unittest.py
new file mode 100644
index 0000000..ff78226
--- /dev/null
+++ b/aidegen/lib/eclipse_project_file_gen_unittest.py
@@ -0,0 +1,124 @@
+#!/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.
+
+"""Unittests for generate project file of Eclipse."""
+
+import os
+import unittest
+from unittest import mock
+
+from aidegen import constant
+from aidegen import unittest_constants
+from aidegen.lib import common_util
+from aidegen.lib import eclipse_project_file_gen
+
+
+# pylint: disable=protected-access
+class EclipseConfUnittests(unittest.TestCase):
+ """Unit tests for generate_project_file.py"""
+ _ROOT_PATH = '/android/root'
+ _PROJECT_RELPATH = 'module/path'
+ _PROJECT_ABSPATH = os.path.join(_ROOT_PATH, _PROJECT_RELPATH)
+ _PROJECT_NAME = 'test'
+ _LINK_TEMPLATE = (' <link><name>%s</name><type>2</type>'
+ '<location>%s</location></link>\n')
+ _PROJECT_SAMPLE = os.path.join(unittest_constants.TEST_DATA_PATH,
+ 'eclipse.project')
+
+ @mock.patch.object(common_util, 'get_android_root_dir')
+ def test_gen_link(self, mock_get_root):
+ """Test get_link return a correct link resource config."""
+ mock_get_root.return_value = self._ROOT_PATH
+ name = os.path.join(constant.KEY_DEPENDENCIES, self._PROJECT_RELPATH)
+ expected_link = self._LINK_TEMPLATE % (name, self._PROJECT_ABSPATH)
+ generated_link = eclipse_project_file_gen.EclipseConf._gen_link(
+ self._PROJECT_RELPATH)
+ self.assertEqual(generated_link, expected_link)
+
+ @mock.patch.object(common_util, 'get_android_root_dir')
+ @mock.patch('aidegen.lib.project_info.ProjectInfo')
+ def test_create_project_content(self, mock_project_info, mock_get_root):
+ """Test _create_project_content."""
+ mock_get_root.return_value = self._ROOT_PATH
+ mock_project_info.project_absolute_path = self._PROJECT_ABSPATH
+ mock_project_info.module_name = self._PROJECT_NAME
+ mock_project_info.source_path = {
+ 'source_folder_path': '',
+ 'test_folder_path': '',
+ 'jar_module_path': {
+ '': self._PROJECT_RELPATH
+ },
+ 'r_java_path': set()
+ }
+ expected_content = common_util.read_file_content(self._PROJECT_SAMPLE)
+ eclipse_config = eclipse_project_file_gen.EclipseConf(mock_project_info)
+ eclipse_config._create_project_content()
+ generated_content = eclipse_config.project_content
+ self.assertEqual(generated_content, expected_content)
+
+ @mock.patch.object(common_util, 'get_android_root_dir')
+ @mock.patch('aidegen.lib.project_info.ProjectInfo')
+ def test_gen_src_path_entries(self, mock_project_info, mock_get_root):
+ """Test generate source folders' class path entries."""
+ mock_get_root.return_value = self._ROOT_PATH
+ mock_project_info.project_absolute_path = self._PROJECT_ABSPATH
+ mock_project_info.project_relative_path = self._PROJECT_RELPATH
+ mock_project_info.module_name = self._PROJECT_NAME
+ mock_project_info.source_path = {
+ 'source_folder_path': set([
+ 'module/path/src',
+ 'module/path/test',
+ ]),
+ 'test_folder_path': set(),
+ 'jar_module_path': {},
+ 'r_java_path': {}
+ }
+ expected_result = [
+ ' <classpathentry kind="src" path="src"/>\n',
+ ' <classpathentry kind="src" path="test"/>\n',
+ ]
+ eclipse_config = eclipse_project_file_gen.EclipseConf(mock_project_info)
+ generated_result = sorted(eclipse_config._gen_src_path_entries())
+ self.assertEqual(generated_result, expected_result)
+
+ @mock.patch.object(common_util, 'get_android_root_dir')
+ @mock.patch('aidegen.lib.project_info.ProjectInfo')
+ def test_gen_jar_path_entries(self, mock_project_info, mock_get_root):
+ """Test generate jar files' class path entries."""
+ mock_get_root.return_value = self._ROOT_PATH
+ mock_project_info.project_absolute_path = self._PROJECT_ABSPATH
+ mock_project_info.project_relative_path = self._PROJECT_RELPATH
+ mock_project_info.module_name = self._PROJECT_NAME
+ mock_project_info.source_path = {
+ 'source_folder_path': set(),
+ 'test_folder_path': set(),
+ 'jar_module_path': {
+ '/abspath/to/the/file.jar': 'relpath/to/the/module',
+ },
+ 'r_java_path': {}
+ }
+ expected_result = [
+ (' <classpathentry exported="true" kind="lib" '
+ 'path="/abspath/to/the/file.jar" '
+ 'sourcepath="dependencies/relpath/to/the/module"/>\n')
+ ]
+ eclipse_config = eclipse_project_file_gen.EclipseConf(mock_project_info)
+ generated_result = eclipse_config._gen_jar_path_entries()
+ self.assertEqual(generated_result, expected_result)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/aidegen/lib/errors.py b/aidegen/lib/errors.py
index 158c433..d7142e9 100644
--- a/aidegen/lib/errors.py
+++ b/aidegen/lib/errors.py
@@ -57,3 +57,7 @@
class FakeModuleError(AIDEgenError):
"""Raised if the module is a fake module."""
+
+
+class InvalidXMLError(AIDEgenError):
+ """Raised if parsing xml file failed."""
diff --git a/aidegen/lib/ide_util.py b/aidegen/lib/ide_util.py
index 43c17d0..750464e 100644
--- a/aidegen/lib/ide_util.py
+++ b/aidegen/lib/ide_util.py
@@ -23,8 +23,8 @@
ide_util_obj = IdeUtil()
if ide_util_obj.is_ide_installed():
- ide_util_obj.config_ide()
- ide_util_obj.launch_ide(project_file)
+ ide_util_obj.config_ide(project_file)
+ ide_util_obj.launch_ide()
"""
import fnmatch
@@ -32,10 +32,13 @@
import logging
import os
import platform
+import re
import subprocess
from aidegen import constant
-from aidegen.lib.config import AidegenConfig
+from aidegen.lib import common_util
+from aidegen.lib import config
+from aidegen.lib import sdk_config
# Add 'nohup' to prevent IDE from being terminated when console is terminated.
_NOHUP = 'nohup'
@@ -43,11 +46,10 @@
_IDEA_FOLDER = '.idea'
_IML_EXTENSION = '.iml'
_JDK_PATH_TOKEN = '@JDKpath'
-_TARGET_JDK_NAME_TAG = '<name value="JDK18" />'
_COMPONENT_END_TAG = ' </component>'
-class IdeUtil():
+class IdeUtil:
"""Provide a set of IDE operations, e.g., launch and configuration.
Attributes:
@@ -55,8 +57,8 @@
For example:
1. Check if IDE is installed.
- 2. Launch an IDE.
- 3. Config IDE, e.g. config code style, SDK path, and etc.
+ 2. Config IDE, e.g. config code style, SDK path, and etc.
+ 3. Launch an IDE.
"""
def __init__(self,
@@ -76,16 +78,17 @@
"""
return self._ide.is_ide_installed()
- def launch_ide(self, project_file):
- """Launches the relative IDE by opening the passed project file.
+ def launch_ide(self):
+ """Launches the relative IDE by opening the passed project file."""
+ return self._ide.launch_ide()
+
+ def config_ide(self, project_abspath):
+ """To config the IDE, e.g., setup code style, init SDK, and etc.
Args:
- project_file: The full path of the IDE project file.
+ project_abspath: An absolute path of the project.
"""
- return self._ide.launch_ide(project_file)
-
- def config_ide(self):
- """To config the IDE, e.g., setup code style, init SDK, and etc."""
+ self._ide.project_abspath = project_abspath
if self.is_ide_installed() and self._ide:
self._ide.apply_optional_config()
@@ -98,7 +101,7 @@
return self._ide.ide_name
-class IdeBase():
+class IdeBase:
"""The most base class of IDE, provides interface and partial path init.
Attributes:
@@ -108,6 +111,7 @@
_bin_paths: A list of all possible IDE executable file absolute paths.
_ide_name: String for IDE name.
_bin_folders: A list of all possible IDE installed paths.
+ project_abspath: The absolute path of the project.
For example:
1. Check if IDE is installed.
@@ -122,6 +126,7 @@
self._bin_file_name = ''
self._bin_paths = []
self._bin_folders = []
+ self.project_abspath = ''
def is_ide_installed(self):
"""Checks if IDE is already installed.
@@ -131,14 +136,9 @@
"""
return bool(self._installed_path)
- def launch_ide(self, project_file):
- """Launches IDE by opening the passed project file.
-
- Args:
- project_file: The full path of the IDE's project file.
- """
- _launch_ide(project_file, self._get_ide_cmd(project_file),
- self._ide_name)
+ def launch_ide(self):
+ """Launches IDE by opening the passed project file."""
+ _launch_ide(self.project_abspath, self._get_ide_cmd(), self._ide_name)
def apply_optional_config(self):
"""Handles IDE relevant configs."""
@@ -154,16 +154,13 @@
"""Gets IDE name."""
return self._ide_name
- def _get_ide_cmd(self, project_file):
+ def _get_ide_cmd(self):
"""Compose launch IDE command to run a new process and redirect output.
- Args:
- project_file: The full path of the IDE's project file.
-
Returns:
A string of launch IDE command.
"""
- return _get_run_ide_cmd(self._installed_path, project_file)
+ return _get_run_ide_cmd(self._installed_path, self.project_abspath)
def _init_installed_path(self, installed_path):
"""Initialize IDE installed path.
@@ -197,7 +194,8 @@
_JDK_PATH: The path of JDK in android project.
_IDE_JDK_TABLE_PATH: The path of JDK table which record JDK info in IDE.
_JDK_PART_TEMPLATE_PATH: The path of the template of partial JDK table.
- _JDK_FULL_TEMPLATE_PATH: The path of the template of full JDK table.
+ _SYMBOLIC_VERSIONS: A string list of the symbolic link paths of
+ IntelliJ.
For example:
1. Check if IntelliJ is installed.
@@ -208,7 +206,8 @@
_JDK_PATH = ''
_IDE_JDK_TABLE_PATH = ''
_JDK_PART_TEMPLATE_PATH = ''
- _JDK_FULL_TEMPLATE_PATH = ''
+ _DEFAULT_ANDROID_SDK_PATH = ''
+ _SYMBOLIC_VERSIONS = []
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
@@ -230,7 +229,12 @@
return
for _config_path in _path_list:
- self._set_jdk_config(_config_path)
+ jdk_file = os.path.join(_config_path, self._IDE_JDK_TABLE_PATH)
+ jdk_table = sdk_config.SDKConfig(
+ jdk_file, self._JDK_PART_TEMPLATE_PATH, self._JDK_PATH,
+ self._DEFAULT_ANDROID_SDK_PATH)
+ jdk_table.config_jdk_file()
+ jdk_table.gen_enable_debugger_module(self.project_abspath)
def _get_config_root_paths(self):
"""Get the config root paths from derived class.
@@ -249,36 +253,6 @@
"""
raise NotImplementedError('Method overriding is needed.')
- def _set_jdk_config(self, path):
- """Add jdk path to jdk.table.xml
-
- Args:
- path: The path of IntelliJ config path.
- """
- jdk_table_path = os.path.join(path, self._IDE_JDK_TABLE_PATH)
- try:
- if os.path.isfile(jdk_table_path):
- with open(jdk_table_path, 'r+') as jdk_table_fd:
- jdk_table = jdk_table_fd.read()
- jdk_table_fd.seek(0)
- if _TARGET_JDK_NAME_TAG not in jdk_table:
- with open(self._JDK_PART_TEMPLATE_PATH) as template_fd:
- template = template_fd.read()
- template = template.replace(_JDK_PATH_TOKEN,
- self._JDK_PATH)
- jdk_table = jdk_table.replace(
- _COMPONENT_END_TAG, template)
- jdk_table_fd.truncate()
- jdk_table_fd.write(jdk_table)
- else:
- with open(self._JDK_FULL_TEMPLATE_PATH) as template_fd:
- template = template_fd.read()
- template = template.replace(_JDK_PATH_TOKEN, self._JDK_PATH)
- with open(jdk_table_path, 'w') as jdk_table_fd:
- jdk_table_fd.write(template)
- except IOError as err:
- logging.warning(err)
-
def _get_preferred_version(self):
"""Get users' preferred IntelliJ version.
@@ -291,22 +265,66 @@
Returns:
The sh full path, or None if no IntelliJ version is installed.
"""
- cefiles = _get_intellij_version_path(self._ls_ce_path)
- uefiles = _get_intellij_version_path(self._ls_ue_path)
- all_versions = self._get_all_versions(cefiles, uefiles)
+ ce_paths = _get_intellij_version_path(self._ls_ce_path)
+ ue_paths = _get_intellij_version_path(self._ls_ue_path)
+ all_versions = self._get_all_versions(ce_paths, ue_paths)
if len(all_versions) > 1:
- with AidegenConfig() as aconf:
+ with config.AidegenConfig() as aconf:
if not self._config_reset and (
aconf.preferred_version in all_versions):
return aconf.preferred_version
- preferred = _ask_preference(all_versions)
+ display_versions = self._merge_symbolic_version(all_versions)
+ preferred = _ask_preference(display_versions)
if preferred:
- aconf.preferred_version = preferred
- return preferred
+ aconf.preferred_version = self._get_real_path(preferred)
+ return aconf.preferred_version
elif all_versions:
return all_versions[0]
return None
+ @staticmethod
+ def _merge_symbolic_version(versions):
+ """Merge the duplicate version of symbolic links.
+
+ Stable and beta versions are a symbolic link to a version.
+ This function combine two versions to one.
+ Ex:
+ ['/opt/intellij-ce-stable/bin/idea.sh',
+ '/opt/intellij-ce-2019.1/bin/idea.sh'] to
+ ['/opt/intellij-ce-stable/bin/idea.sh ->
+ /opt/intellij-ce-2019.1/bin/idea.sh',
+ '/opt/intellij-ce-2019.1/bin/idea.sh']
+
+ Args:
+ versions: A list of all installed versions.
+
+ Returns:
+ A list of versions to show for user to select. It may contain
+ 'symbolic_path/idea.sh -> original_path/idea.sh'.
+ """
+ display_versions = versions[:]
+ for symbolic_version in IdeIntelliJ._SYMBOLIC_VERSIONS:
+ if symbolic_version in display_versions and os.path.isfile(
+ symbolic_version):
+ real_path = os.path.realpath(symbolic_version)
+ for index, path in enumerate(display_versions):
+ if path == symbolic_version:
+ display_versions[index] = ' -> '.join(
+ [display_versions[index], real_path])
+ break
+ return display_versions
+
+ @staticmethod
+ def _get_real_path(path):
+ """ Get real path from merged path.
+
+ Args:
+ path: A path string may be merged with symbolic path.
+ Returns:
+ The real IntelliJ installed path.
+ """
+ return path.split()[0]
+
def _get_script_from_system(self):
"""Get correct IntelliJ installed path from internal path.
@@ -336,46 +354,40 @@
all_versions.extend(uefiles)
return all_versions
- @staticmethod
- def _get_code_style_config():
- """Get Android build-in IntelliJ code style config file.
-
- Returns:
- None if the file is not found, otherwise a full path string of
- Intellij Android code style file.
- """
- _config_source = os.path.join(constant.ANDROID_ROOT_PATH, 'development',
- 'ide', 'intellij', 'codestyles',
- 'AndroidStyle.xml')
-
- return _config_source if os.path.isfile(_config_source) else None
-
class IdeLinuxIntelliJ(IdeIntelliJ):
"""Provide the IDEA behavior implementation for OS Linux.
+ Class Attributes:
+ _INTELLIJ_RE: Regular expression of IntelliJ installed name in GLinux.
+
For example:
1. Check if IntelliJ is installed.
2. Launch an IntelliJ.
3. Config IntelliJ.
"""
- _JDK_PATH = os.path.join(constant.ANDROID_ROOT_PATH,
+ _JDK_PATH = os.path.join(common_util.get_android_root_dir(),
'prebuilts/jdk/jdk8/linux-x86')
# TODO(b/127899277): Preserve a config for jdk version option case.
_IDE_JDK_TABLE_PATH = 'config/options/jdk.table.xml'
_JDK_PART_TEMPLATE_PATH = os.path.join(
- constant.AIDEGEN_ROOT_PATH, 'templates/jdkTable/part.jdk.table.xml')
- _JDK_FULL_TEMPLATE_PATH = os.path.join(constant.AIDEGEN_ROOT_PATH,
- 'templates/jdkTable/jdk.table.xml')
+ common_util.get_aidegen_root_dir(),
+ 'templates/jdkTable/part.jdk.table.xml')
+ _DEFAULT_ANDROID_SDK_PATH = os.path.join(os.getenv('HOME'), 'Android/Sdk')
+ IdeIntelliJ._SYMBOLIC_VERSIONS = ['/opt/intellij-ce-stable/bin/idea.sh',
+ '/opt/intellij-ue-stable/bin/idea.sh',
+ '/opt/intellij-ce-beta/bin/idea.sh',
+ '/opt/intellij-ue-beta/bin/idea.sh']
+ _INTELLIJ_RE = re.compile(r'intellij-(ce|ue)-')
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
self._bin_file_name = 'idea.sh'
self._bin_folders = ['/opt/intellij-*/bin']
- self._ls_ce_path = os.path.join('/opt/intellij-ce-2*/bin',
+ self._ls_ce_path = os.path.join('/opt/intellij-ce-*/bin',
self._bin_file_name)
- self._ls_ue_path = os.path.join('/opt/intellij-ue-2*/bin',
+ self._ls_ue_path = os.path.join('/opt/intellij-ue-*/bin',
self._bin_file_name)
self._init_installed_path(installed_path)
@@ -395,16 +407,12 @@
_config_folders = []
_config_folder = ''
- # TODO(b/123459239): For the case that the user provides the IDEA
- # binary path, we now collect all possible IDEA config root paths.
- if 'e-20' not in self._installed_path:
- _config_folders = glob.glob(
- os.path.join(os.getenv('HOME'), '.IdeaI?20*'))
- _config_folders.extend(
- glob.glob(os.path.join(os.getenv('HOME'), '.IntelliJIdea20*')))
- logging.info('The config path list: %s.\n', _config_folders)
- else:
- _path_data = self._installed_path.split('-')
+ if IdeLinuxIntelliJ._INTELLIJ_RE.search(self._installed_path):
+ # GLinux case.
+ if self._installed_path in IdeIntelliJ._SYMBOLIC_VERSIONS:
+ _path_data = os.path.realpath(self._installed_path).split('-')
+ else:
+ _path_data = self._installed_path.split('-')
_ide_version = _path_data[2].split(os.sep)[0]
if _path_data[1] == 'ce':
_config_folder = ''.join(['.IdeaIC', _ide_version])
@@ -413,6 +421,15 @@
_config_folders.append(
os.path.join(os.getenv('HOME'), _config_folder))
+ else:
+ # TODO(b/123459239): For the case that the user provides the IDEA
+ # binary path, we now collect all possible IDEA config root paths.
+ _config_folders = glob.glob(
+ os.path.join(os.getenv('HOME'), '.IdeaI?20*'))
+ _config_folders.extend(
+ glob.glob(os.path.join(os.getenv('HOME'), '.IntelliJIdea20*')))
+ logging.info('The config path list: %s.', _config_folders)
+
return _config_folders
def _get_config_folder_name(self):
@@ -433,13 +450,14 @@
3. Config IntelliJ.
"""
- _JDK_PATH = os.path.join(constant.ANDROID_ROOT_PATH,
+ _JDK_PATH = os.path.join(common_util.get_android_root_dir(),
'prebuilts/jdk/jdk8/darwin-x86')
_IDE_JDK_TABLE_PATH = 'options/jdk.table.xml'
_JDK_PART_TEMPLATE_PATH = os.path.join(
- constant.AIDEGEN_ROOT_PATH, 'templates/jdkTable/part.mac.jdk.table.xml')
- _JDK_FULL_TEMPLATE_PATH = os.path.join(
- constant.AIDEGEN_ROOT_PATH, 'templates/jdkTable/mac.jdk.table.xml')
+ common_util.get_aidegen_root_dir(),
+ 'templates/jdkTable/part.mac.jdk.table.xml')
+ _DEFAULT_ANDROID_SDK_PATH = os.path.join(
+ os.getenv('HOME'), 'Library/Android/sdk')
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
@@ -541,7 +559,7 @@
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
self._ide_name = constant.IDE_ECLIPSE
- self._bin_file_name = 'eclipse*'
+ self._bin_file_name = 'eclipse'
def _get_script_from_system(self):
"""Get correct IDE installed path from internal path.
@@ -554,7 +572,10 @@
The sh full path, or None if no IntelliJ version is installed.
"""
for ide_path in self._bin_paths:
- ls_output = glob.glob(ide_path, recursive=True)
+ # The binary name of Eclipse could be eclipse47, eclipse49,
+ # eclipse47_testing or eclipse49_testing. So finding the matched
+ # binary by /path/to/ide/eclipse*.
+ ls_output = glob.glob(ide_path + '*', recursive=True)
if ls_output:
ls_output = sorted(ls_output)
match_eclipses = []
@@ -595,25 +616,20 @@
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
- self._bin_file_name = 'Eclipse.app'
+ self._bin_file_name = 'eclipse'
self._bin_folders = [os.path.expanduser('~/eclipse/**')]
self._bin_paths = self._get_possible_bin_paths()
self._init_installed_path(installed_path)
- def _get_ide_cmd(self, project_file):
+ def _get_ide_cmd(self):
"""Compose launch IDE command to run a new process and redirect output.
- Args:
- project_file: The full path of the IDE's project file.
-
Returns:
A string of launch IDE command.
"""
return ' '.join([
- _NOHUP,
- 'open',
self._installed_path.replace(' ', r'\ '),
- os.path.dirname(project_file), _IGNORE_STD_OUT_ERR_CMD, '&'
+ os.path.dirname(self.project_abspath), _IGNORE_STD_OUT_ERR_CMD, '&'
])
@@ -670,9 +686,11 @@
logging.debug('Search all files under %s to get %s, %s.', top, root,
files)
for file_ in fnmatch.filter(files, ide_script_name):
- logging.debug('Use file name filter to find %s in path %s.', file_,
- os.path.join(root, file_))
- yield os.path.join(root, file_)
+ exe_file = os.path.join(root, file_)
+ if os.access(exe_file, os.X_OK):
+ logging.debug('Use file name filter to find %s in path %s.',
+ file_, exe_file)
+ yield exe_file
def _get_run_ide_cmd(sh_path, project_file):
@@ -688,10 +706,7 @@
# In command usage, the space ' ' should be '\ ' for correctness.
return ' '.join([
_NOHUP,
- sh_path.replace(' ', r'\ '),
- project_file,
- _IGNORE_STD_OUT_ERR_CMD,
- '&'
+ sh_path.replace(' ', r'\ '), project_file, _IGNORE_STD_OUT_ERR_CMD, '&'
])
@@ -724,7 +739,8 @@
"""
logging.debug('Call _get_script_from_dir_path with %s, and %s', input_path,
ide_file_name)
- files_found = list(_walk_tree_find_ide_exe_file(input_path, ide_file_name))
+ files_found = list(_walk_tree_find_ide_exe_file(input_path,
+ ide_file_name + '*'))
if files_found:
return sorted(files_found)[0]
return None
@@ -840,7 +856,7 @@
for i in range(len(all_versions)):
all_numbers.append(str(i + 1))
input_data = input(query)
- while not input_data in all_numbers:
+ while input_data not in all_numbers:
input_data = input('Please select a number:\t')
return all_versions[int(input_data) - 1]
diff --git a/aidegen/lib/ide_util_unittest.py b/aidegen/lib/ide_util_unittest.py
index ee79a5e..c741d92 100644
--- a/aidegen/lib/ide_util_unittest.py
+++ b/aidegen/lib/ide_util_unittest.py
@@ -18,29 +18,21 @@
"""Unittests for ide_util."""
import os
+import shutil
+import subprocess
+import tempfile
import unittest
+
from unittest import mock
-from unittest.mock import patch
-from subprocess import CalledProcessError as cmd_err
-
-from aidegen.lib.android_dev_os import AndroidDevOS
+from aidegen import unittest_constants
+from aidegen.lib import android_dev_os
from aidegen.lib import ide_util
-from aidegen.lib.ide_util import IdeBase
-from aidegen.lib.ide_util import IdeIntelliJ
-from aidegen.lib.ide_util import IdeLinuxEclipse
-from aidegen.lib.ide_util import IdeLinuxIntelliJ
-from aidegen.lib.ide_util import IdeLinuxStudio
-from aidegen.lib.ide_util import IdeMacEclipse
-from aidegen.lib.ide_util import IdeMacIntelliJ
-from aidegen.lib.ide_util import IdeMacStudio
-from aidegen.lib.ide_util import IdeUtil
+from aidegen.lib import sdk_config
-import aidegen.unittest_constants as uc
-
-
-#pylint: disable=protected-access
+# pylint: disable=protected-access
+# pylint: disable-msg=too-many-arguments
class IdeUtilUnittests(unittest.TestCase):
"""Unit tests for ide_util.py."""
@@ -48,18 +40,19 @@
_TEST_PRJ_PATH2 = ''
_TEST_PRJ_PATH3 = ''
_TEST_PRJ_PATH4 = ''
-
+ _MODULE_XML_SAMPLE = ''
def setUp(self):
"""Prepare the testdata related path."""
- IdeUtilUnittests._TEST_PRJ_PATH1 = os.path.join(uc.TEST_DATA_PATH,
- 'android_facet.iml')
- IdeUtilUnittests._TEST_PRJ_PATH2 = os.path.join(uc.TEST_DATA_PATH,
- 'project/test.java')
- IdeUtilUnittests._TEST_PRJ_PATH3 = uc.TEST_DATA_PATH
- IdeUtilUnittests._TEST_PRJ_PATH4 = os.path.join(uc.TEST_DATA_PATH,
- '.idea')
-
+ IdeUtilUnittests._TEST_PRJ_PATH1 = os.path.join(
+ unittest_constants.TEST_DATA_PATH, 'android_facet.iml')
+ IdeUtilUnittests._TEST_PRJ_PATH2 = os.path.join(
+ unittest_constants.TEST_DATA_PATH, 'project/test.java')
+ IdeUtilUnittests._TEST_PRJ_PATH3 = unittest_constants.TEST_DATA_PATH
+ IdeUtilUnittests._TEST_PRJ_PATH4 = os.path.join(
+ unittest_constants.TEST_DATA_PATH, '.idea')
+ IdeUtilUnittests._MODULE_XML_SAMPLE = os.path.join(
+ unittest_constants.TEST_DATA_PATH, 'modules.xml')
def tearDown(self):
"""Clear the testdata related path."""
@@ -79,38 +72,43 @@
self.assertFalse(
ide_util._is_intellij_project(IdeUtilUnittests._TEST_PRJ_PATH4))
- @mock.patch('glob.glob', return_value=uc.IDEA_SH_FIND_NONE)
+ @mock.patch('glob.glob', return_value=unittest_constants.IDEA_SH_FIND_NONE)
def test_get_intellij_sh_none(self, mock_glob):
"""Test with the cmd return none, test result should be None."""
- mock_glob.return_value = uc.IDEA_SH_FIND_NONE
+ mock_glob.return_value = unittest_constants.IDEA_SH_FIND_NONE
self.assertEqual(
None,
- ide_util._get_intellij_version_path(IdeLinuxIntelliJ()._ls_ce_path))
+ ide_util._get_intellij_version_path(
+ ide_util.IdeLinuxIntelliJ()._ls_ce_path))
self.assertEqual(
None,
- ide_util._get_intellij_version_path(IdeLinuxIntelliJ()._ls_ue_path))
+ ide_util._get_intellij_version_path(
+ ide_util.IdeLinuxIntelliJ()._ls_ue_path))
@mock.patch('builtins.input')
- @mock.patch('glob.glob', return_value=uc.IDEA_SH_FIND)
+ @mock.patch('glob.glob', return_value=unittest_constants.IDEA_SH_FIND)
def test_ask_preference(self, mock_glob, mock_input):
"""Ask users' preference, the result should be equal to test data."""
- mock_glob.return_value = uc.IDEA_SH_FIND
+ mock_glob.return_value = unittest_constants.IDEA_SH_FIND
mock_input.return_value = '1'
self.assertEqual(
- ide_util._ask_preference(uc.IDEA_SH_FIND), uc.IDEA_SH_FIND[0])
+ ide_util._ask_preference(unittest_constants.IDEA_SH_FIND),
+ unittest_constants.IDEA_SH_FIND[0])
mock_input.return_value = '2'
self.assertEqual(
- ide_util._ask_preference(uc.IDEA_SH_FIND), uc.IDEA_SH_FIND[1])
+ ide_util._ask_preference(unittest_constants.IDEA_SH_FIND),
+ unittest_constants.IDEA_SH_FIND[1])
@unittest.skip('Skip to use real command to launch IDEA.')
def test_run_intellij_sh_in_linux(self):
"""Follow the target behavior, with sh to show UI, else raise err."""
- sh_path = IdeLinuxIntelliJ()._get_script_from_system()
+ sh_path = ide_util.IdeLinuxIntelliJ()._get_script_from_system()
if sh_path:
- ide_util_obj = IdeUtil()
- ide_util_obj.launch_ide(IdeUtilUnittests._TEST_PRJ_PATH1)
+ ide_util_obj = ide_util.IdeUtil()
+ ide_util_obj.config_ide(IdeUtilUnittests._TEST_PRJ_PATH1)
+ ide_util_obj.launch_ide()
else:
- self.assertRaises(cmd_err)
+ self.assertRaises(subprocess.CalledProcessError)
@mock.patch.object(ide_util, '_get_linux_ide')
@mock.patch.object(ide_util, '_get_mac_ide')
@@ -121,101 +119,147 @@
ide_util._get_ide(None, 'j', False, is_mac=False)
self.assertTrue(mock_linux.called)
- def test_get_mac_and_linux_ide(self):
+ @mock.patch.object(ide_util.IdeIntelliJ, '_get_preferred_version')
+ def test_get_mac_and_linux_ide(self, mock_preference):
"""Test if _get_mac_ide and _get_linux_ide return correct IDE class."""
- self.assertIsInstance(ide_util._get_mac_ide(), IdeMacIntelliJ)
- self.assertIsInstance(ide_util._get_mac_ide(None, 's'), IdeMacStudio)
- self.assertIsInstance(ide_util._get_mac_ide(None, 'e'), IdeMacEclipse)
- self.assertIsInstance(ide_util._get_linux_ide(), IdeLinuxIntelliJ)
+ mock_preference.return_value = None
+ self.assertIsInstance(ide_util._get_mac_ide(), ide_util.IdeMacIntelliJ)
+ self.assertIsInstance(ide_util._get_mac_ide(None, 's'),
+ ide_util.IdeMacStudio)
+ self.assertIsInstance(ide_util._get_mac_ide(None, 'e'),
+ ide_util.IdeMacEclipse)
+ self.assertIsInstance(ide_util._get_linux_ide(),
+ ide_util.IdeLinuxIntelliJ)
self.assertIsInstance(
- ide_util._get_linux_ide(None, 's'), IdeLinuxStudio)
+ ide_util._get_linux_ide(None, 's'), ide_util.IdeLinuxStudio)
self.assertIsInstance(
- ide_util._get_linux_ide(None, 'e'), IdeLinuxEclipse)
+ ide_util._get_linux_ide(None, 'e'), ide_util.IdeLinuxEclipse)
@mock.patch.object(ide_util, '_get_script_from_input_path')
- @mock.patch.object(IdeIntelliJ, '_get_script_from_system')
+ @mock.patch.object(ide_util.IdeIntelliJ, '_get_script_from_system')
def test_init_ideintellij(self, mock_sys, mock_input):
"""Test IdeIntelliJ's __init__ method."""
- IdeLinuxIntelliJ()
+ ide_util.IdeLinuxIntelliJ()
self.assertTrue(mock_sys.called)
- IdeMacIntelliJ()
+ ide_util.IdeMacIntelliJ()
self.assertTrue(mock_sys.called)
- IdeLinuxIntelliJ('some_path')
+ ide_util.IdeLinuxIntelliJ('some_path')
self.assertTrue(mock_input.called)
- IdeMacIntelliJ('some_path')
+ ide_util.IdeMacIntelliJ('some_path')
self.assertTrue(mock_input.called)
- @mock.patch.object(IdeIntelliJ, '_get_config_root_paths')
- @mock.patch.object(IdeBase, 'apply_optional_config')
- def test_config_ide(self, mock_config, mock_paths):
+ @mock.patch.object(ide_util.IdeIntelliJ, '_get_preferred_version')
+ @mock.patch.object(sdk_config.SDKConfig, '_android_sdk_exists')
+ @mock.patch.object(sdk_config.SDKConfig, '_target_jdk_exists')
+ @mock.patch.object(ide_util.IdeIntelliJ, '_get_config_root_paths')
+ @mock.patch.object(ide_util.IdeBase, 'apply_optional_config')
+ def test_config_ide(self, mock_config, mock_paths, mock_jdk, mock_sdk,
+ mock_preference):
"""Test IDEA, IdeUtil.config_ide won't call base none implement api."""
- util_obj = IdeUtil()
- util_obj.config_ide()
- self.assertFalse(mock_config.called)
- self.assertFalse(mock_paths.called)
+ # Mock SDkConfig flow to not to generate real jdk config file.
+ mock_preference.return_value = None
+ mock_jdk.return_value = True
+ mock_sdk.return_value = True
+ test_path = os.path.join(tempfile.mkdtemp())
+ module_path = os.path.join(test_path, 'test')
+ idea_path = os.path.join(module_path, '.idea')
+ os.makedirs(idea_path)
+ shutil.copy(IdeUtilUnittests._MODULE_XML_SAMPLE, idea_path)
+ try:
+ util_obj = ide_util.IdeUtil()
+ util_obj.config_ide(module_path)
+ self.assertFalse(mock_config.called)
+ self.assertFalse(mock_paths.called)
+ finally:
+ shutil.rmtree(test_path)
- @patch.object(ide_util, '_get_script_from_input_path')
- @patch.object(ide_util, '_get_script_from_internal_path')
+ @mock.patch.object(ide_util, '_get_script_from_input_path')
+ @mock.patch.object(ide_util, '_get_script_from_internal_path')
def test_get_linux_config_1(self, mock_path, mock_path_2):
"""Test to get unique config path for linux IDEA case."""
- if not AndroidDevOS.MAC == AndroidDevOS.get_os_type():
- mock_path.return_value = '/opt/intelliJ-ce-2018.3/bin/idea.sh'
- mock_path_2.return_value = '/opt/intelliJ-ce-2018.3/bin/idea.sh'
- ide_obj = IdeLinuxIntelliJ()
+ if (not android_dev_os.AndroidDevOS.MAC ==
+ android_dev_os.AndroidDevOS.get_os_type()):
+ mock_path.return_value = '/opt/intellij-ce-2018.3/bin/idea.sh'
+ mock_path_2.return_value = '/opt/intellij-ce-2018.3/bin/idea.sh'
+ ide_obj = ide_util.IdeLinuxIntelliJ('default_path')
self.assertEqual(1, len(ide_obj._get_config_root_paths()))
else:
- self.assertTrue(AndroidDevOS.MAC == AndroidDevOS.get_os_type())
+ self.assertTrue((android_dev_os.AndroidDevOS.MAC ==
+ android_dev_os.AndroidDevOS.get_os_type()))
- @patch('glob.glob')
- @patch.object(ide_util, '_get_script_from_input_path')
- @patch.object(ide_util, '_get_script_from_internal_path')
+ @mock.patch('glob.glob')
+ @mock.patch.object(ide_util, '_get_script_from_input_path')
+ @mock.patch.object(ide_util, '_get_script_from_internal_path')
def test_get_linux_config_2(self, mock_path, mock_path_2, mock_filter):
"""Test to get unique config path for linux IDEA case."""
- if not AndroidDevOS.MAC == AndroidDevOS.get_os_type():
+ if (not android_dev_os.AndroidDevOS.MAC ==
+ android_dev_os.AndroidDevOS.get_os_type()):
mock_path.return_value = '/opt/intelliJ-ce-2018.3/bin/idea.sh'
mock_path_2.return_value = '/opt/intelliJ-ce-2018.3/bin/idea.sh'
- ide_obj = IdeLinuxIntelliJ()
+ ide_obj = ide_util.IdeLinuxIntelliJ()
mock_filter.called = False
ide_obj._get_config_root_paths()
self.assertFalse(mock_filter.called)
else:
- self.assertTrue(AndroidDevOS.MAC == AndroidDevOS.get_os_type())
+ self.assertTrue((android_dev_os.AndroidDevOS.MAC ==
+ android_dev_os.AndroidDevOS.get_os_type()))
def test_get_mac_config_root_paths(self):
"""Return None if there's no install path."""
- if AndroidDevOS.MAC == AndroidDevOS.get_os_type():
- mac_ide = IdeMacIntelliJ()
+ if (android_dev_os.AndroidDevOS.MAC ==
+ android_dev_os.AndroidDevOS.get_os_type()):
+ mac_ide = ide_util.IdeMacIntelliJ()
mac_ide._installed_path = None
self.assertIsNone(mac_ide._get_config_root_paths())
else:
- self.assertFalse(AndroidDevOS.MAC == AndroidDevOS.get_os_type())
+ self.assertFalse((android_dev_os.AndroidDevOS.MAC ==
+ android_dev_os.AndroidDevOS.get_os_type()))
- @patch('glob.glob')
- @patch.object(ide_util, '_get_script_from_input_path')
- @patch.object(ide_util, '_get_script_from_internal_path')
+ @mock.patch('glob.glob')
+ @mock.patch.object(ide_util, '_get_script_from_input_path')
+ @mock.patch.object(ide_util, '_get_script_from_internal_path')
def test_get_linux_config_root(self, mock_path_1, mock_path_2, mock_filter):
"""Test to go filter logic for self download case."""
mock_path_1.return_value = '/usr/tester/IDEA/IC2018.3.3/bin'
mock_path_2.return_value = '/usr/tester/IDEA/IC2018.3.3/bin'
- ide_obj = IdeLinuxIntelliJ()
+ ide_obj = ide_util.IdeLinuxIntelliJ()
mock_filter.reset()
ide_obj._get_config_root_paths()
self.assertTrue(mock_filter.called)
- @patch('os.path.join')
- def test_get_code_style_config(self, mock_join_path):
- """Test return None, when no config source case existed."""
- mock_join_path.return_value = '/usr/tester/no_file.test'
- self.assertIsNone(ide_util.IdeIntelliJ._get_code_style_config())
+ @mock.patch.object(sdk_config.SDKConfig, '__init__')
+ @mock.patch.object(ide_util.IdeIntelliJ, '_get_config_root_paths')
+ def test_apply_optional_config(self, mock_path, mock_conf):
+ """Test basic logic of _apply_optional_config."""
+ with mock.patch.object(ide_util, 'IdeIntelliJ') as obj:
+ obj._installed_path = None
+ obj.apply_optional_config()
+ self.assertFalse(mock_path.called)
- @patch('shutil.copy2')
- @patch.object(IdeIntelliJ, '_get_code_style_config')
- def test_apply_optional_config(self, mock_config_path, mock_copy):
- """Test copy logic should not work if there's no config source."""
- mock_config_path.return_value = None
- ide_obj = IdeIntelliJ()
- ide_obj.apply_optional_config()
- self.assertFalse(mock_copy.called)
+ obj.reset()
+ obj._installed_path = 'default_path'
+ mock_path.return_value = ['path1', 'path2']
+ obj._IDE_JDK_TABLE_PATH = '/JDK_path'
+
+ obj.apply_optional_config()
+ self.assertTrue(mock_conf.called_with('path1/JDK_path'))
+ self.assertTrue(mock_conf.called_with('path2/JDK_path'))
+
+ @mock.patch('os.path.realpath')
+ @mock.patch('os.path.isfile')
+ def test_merge_symbolic_version(self, mock_isfile, mock_realpath):
+ """Test _merge_symbolic_version and _get_real_path."""
+ symbolic_path = ide_util.IdeIntelliJ._SYMBOLIC_VERSIONS[0]
+ original_path = 'intellij-ce-2019.1/bin/idea.sh'
+ mock_isfile.return_value = True
+ mock_realpath.return_value = original_path
+ ide_obj = ide_util.IdeLinuxIntelliJ('default_path')
+ merged_version = ide_obj._merge_symbolic_version(
+ [symbolic_path, original_path])
+ self.assertEqual(
+ merged_version[0], symbolic_path + ' -> ' + original_path)
+ self.assertEqual(
+ ide_obj._get_real_path(merged_version[0]), symbolic_path)
if __name__ == '__main__':
diff --git a/aidegen/lib/metrics.py b/aidegen/lib/metrics.py
deleted file mode 100644
index a03db43..0000000
--- a/aidegen/lib/metrics.py
+++ /dev/null
@@ -1,147 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2018 - 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.
-
-"""AIDEgen metrics functions."""
-
-import json
-import logging
-import os
-import platform
-import subprocess
-import sys
-import urllib.request
-import uuid
-
-from aidegen import constant
-from atest import atest_utils
-from metrics import metrics
-from metrics import metrics_base
-from metrics import metrics_utils
-
-_METRICS_URL = 'http://asuite-218222.appspot.com/aidegen/metrics'
-_VALID_DOMAINS = ['google.com', 'android.com']
-_COMMAND_GIT_CONFIG = ['git', 'config', '--get', 'user.email']
-_JSON_HEADERS = {'Content-Type': 'application/json'}
-_METRICS_RESPONSE = b'done'
-_DUMMY_UUID = '00000000-0000-4000-8000-000000000000'
-_METRICS_TIMEOUT = 2 #seconds
-_META_FILE = os.path.join(
- os.path.expanduser('~'), '.config', 'asuite', '.metadata')
-
-
-def log_usage():
- """Log aidegen run."""
- # Show privacy and license hint message before collect data.
- atest_utils.print_data_collection_notice()
- _log_event(_METRICS_URL, dummy_key_fallback=False, ldap=_get_ldap())
-
-
-def starts_asuite_metrics():
- """Starts to record metrics data.
-
- Send a metrics data to log server at the same time.
- """
- metrics_base.MetricsBase.tool_name = constant.AIDEGEN_TOOL_NAME
- metrics_utils.get_start_time()
- command = ' '.join(sys.argv)
- metrics.AtestStartEvent(
- command_line=command,
- test_references=[],
- cwd=os.getcwd(),
- os=platform.platform())
-
-
-def ends_asuite_metrics(exit_code, stacktrace='', logs=''):
- """Send the end event to log server.
-
- Args:
- exit_code: An integer of exit code.
- stacktrace: A string of stacktrace.
- logs: A string of logs.
- """
- metrics_utils.send_exit_event(
- exit_code,
- stacktrace=stacktrace,
- logs=logs)
-
-
-# pylint: disable=broad-except
-def _get_ldap():
- """Return string email username for valid domains only, None otherwise."""
- if not atest_utils.is_external_run():
- aidegen_project = os.path.join(constant.ANDROID_ROOT_PATH, 'tools',
- 'asuite', 'aidegen')
- email = subprocess.check_output(
- _COMMAND_GIT_CONFIG, cwd=aidegen_project).strip()
- ldap, domain = str(email, encoding="utf-8").split('@', 2)
- if domain in _VALID_DOMAINS:
- return ldap
- return None
-
-
-# pylint: disable=broad-except
-def _log_event(metrics_url, dummy_key_fallback=True, **kwargs):
- """Base log event function for asuite backend.
-
- Args:
- metrics_url: String, URL to report metrics to.
- dummy_key_fallback: Boolean, If True and unable to get grouping key,
- use a dummy key otherwise return out. Sometimes we
- don't want to return metrics for users we are
- unable to identify. Default True.
- kwargs: Dict, additional fields we want to return metrics for.
- """
- try:
- try:
- key = str(_get_grouping_key())
- except Exception:
- if not dummy_key_fallback:
- return
- key = _DUMMY_UUID
- data = {'grouping_key': key, 'run_id': str(uuid.uuid4())}
- if kwargs:
- data.update(kwargs)
- data = json.dumps(data).encode("utf-8")
- request = urllib.request.Request(
- metrics_url, data=data, headers=_JSON_HEADERS)
- response = urllib.request.urlopen(request, timeout=_METRICS_TIMEOUT)
- content = response.read()
- if content != _METRICS_RESPONSE:
- raise Exception('Unexpected metrics response: %s' % content)
- except Exception:
- logging.exception('Exception sending metrics')
-
-
-def _get_grouping_key():
- """Get grouping key. Returns UUID.uuid4."""
- if os.path.isfile(_META_FILE):
- with open(_META_FILE) as meta_file:
- try:
- return uuid.UUID(meta_file.read(), version=4)
- except ValueError:
- logging.exception('malformed group_key in file, rewriting')
-
- key = uuid.uuid4()
- dir_path = os.path.dirname(_META_FILE)
- if os.path.isfile(dir_path):
- os.remove(dir_path)
- try:
- os.makedirs(dir_path)
- except OSError as err:
- if not os.path.isdir(dir_path):
- raise err
- with open(_META_FILE, 'w+') as meta_file:
- meta_file.write(str(key))
- return key
diff --git a/aidegen/lib/metrics_unittest.py b/aidegen/lib/metrics_unittest.py
deleted file mode 100644
index 5ae4790..0000000
--- a/aidegen/lib/metrics_unittest.py
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/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.
-
-"""Unittests for metrics."""
-
-from __future__ import print_function
-
-import unittest
-from unittest import mock
-
-from aidegen.lib import metrics
-from atest import atest_utils
-
-
-class MetricsUnittests(unittest.TestCase):
-
- """Unit tests for metrics.py"""
- @mock.patch.object(atest_utils, 'is_external_run')
- @mock.patch.object(atest_utils, 'print_data_collection_notice')
- def test_log_usage(self, mock_notice, mock_external_check):
- """Test log_usage always run through the target test function."""
- metrics.log_usage()
- self.assertTrue(mock_notice.called)
- self.assertTrue(mock_external_check.called)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/aidegen/lib/module_info.py b/aidegen/lib/module_info.py
new file mode 100644
index 0000000..2e50bb2
--- /dev/null
+++ b/aidegen/lib/module_info.py
@@ -0,0 +1,135 @@
+#!/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.
+
+"""Module Info class used to hold cached merged_module_info.json.json."""
+
+import json
+import logging
+import os
+
+from aidegen import constant
+from aidegen.lib import common_util
+from aidegen.lib import module_info_util
+from atest import constants
+from atest import module_info
+
+
+class AidegenModuleInfo(module_info.ModuleInfo):
+ """Class that offers fast/easy lookup for Module related details.
+
+ Class attributes:
+ mod_info: A ModuleInfo instance contains data of the merged json file
+ after initialization.
+ projects: A list of project names.
+ verbose: A boolean, if true displays full build output.
+ skip_build: A boolean, if true skip building
+ constant.BLUEPRINT_JSONFILE_NAME if it exists, otherwise
+ build it.
+ """
+ mod_info = None
+ projects = []
+ verbose = False
+ skip_build = False
+
+ # pylint: disable=too-many-arguments
+ def __init__(self,
+ force_build=False,
+ module_file=None,
+ atest_module_info=None,
+ projects=None,
+ verbose=False,
+ skip_build=False):
+ """Initialize the AidegenModuleInfo object.
+
+ Load up the module-info.json file and initialize the helper vars.
+
+ Args:
+ force_build: Boolean to indicate if we should rebuild the
+ module_info file regardless if it's created or not.
+ The default value is False: don't force build.
+ module_file: String of path to file to load up. Used for testing.
+ The default value is None: don't specify the path.
+ atest_module_info: A ModuleInfo instance contains data of
+ module-info.json. The default value is None,
+ module_info_util can get it from
+ common_util.get_atest_module_info function.
+ projects: A list of project names. The default value is None,
+ module_info_util won't show reuse iml project file
+ message.
+ verbose: A boolean, if true displays full build output. The default
+ value is False.
+ skip_build: A boolean, if true skip building
+ constant.BLUEPRINT_JSONFILE_NAME if it exists, otherwise
+ build it. The default value is False.
+ """
+ AidegenModuleInfo.mod_info = atest_module_info
+ AidegenModuleInfo.projects = projects
+ AidegenModuleInfo.verbose = verbose
+ AidegenModuleInfo.skip_build = skip_build
+ super().__init__(force_build, module_file)
+
+ @staticmethod
+ def _discover_mod_file_and_target(force_build):
+ """Find the module file.
+
+ If force_build is True, we'll remove module_bp_java_deps.json first and
+ let module_info_util.generate_merged_module_info regenerate it again.
+
+ Args:
+ force_build: Boolean to indicate if we should rebuild the
+ module_info file regardless if it's created or not.
+
+ Returns:
+ Tuple of the relative and absolute paths of the merged module info
+ file.
+ """
+ module_file_path = common_util.get_blueprint_json_path()
+ if force_build and os.path.isfile(module_file_path):
+ os.remove(module_file_path)
+ merged_file_path = os.path.join(common_util.get_soong_out_path(),
+ constant.MERGED_MODULE_INFO)
+ if not os.path.isfile(module_file_path):
+ logging.debug(
+ 'Generating %s - this is required for the initial runs.',
+ merged_file_path)
+ if not AidegenModuleInfo.mod_info:
+ AidegenModuleInfo.mod_info = common_util.get_atest_module_info()
+ data = module_info_util.generate_merged_module_info(
+ AidegenModuleInfo.mod_info, AidegenModuleInfo.projects,
+ AidegenModuleInfo.verbose, AidegenModuleInfo.skip_build)
+ with open(merged_file_path, 'w') as json_file:
+ json.dump(data, json_file, indent=4)
+ module_file_rel_path = os.path.relpath(
+ merged_file_path, common_util.get_android_root_dir())
+ return module_file_rel_path, merged_file_path
+
+ @staticmethod
+ def is_target_module(mod_info):
+ """Determine if the module is a target module.
+
+ Determine if a module's class is in TARGET_CLASSES.
+
+ Args:
+ mod_info: A module's module-info dictionary to be checked.
+
+ Returns:
+ A boolean, true if it is a target module, otherwise false.
+ """
+ if mod_info:
+ return any(
+ x in mod_info.get(constants.MODULE_CLASS, [])
+ for x in constant.TARGET_CLASSES)
+ return False
diff --git a/aidegen/lib/module_info_unittest.py b/aidegen/lib/module_info_unittest.py
new file mode 100644
index 0000000..d874191
--- /dev/null
+++ b/aidegen/lib/module_info_unittest.py
@@ -0,0 +1,40 @@
+#!/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.
+
+"""Unittests for module_info."""
+
+import unittest
+
+from aidegen.lib.module_info import AidegenModuleInfo
+
+
+class AidegenModuleInfoUnittests(unittest.TestCase):
+ """Unit tests for module_info.py"""
+
+ def test_is_target_module(self):
+ """Test is_target_module with different conditions."""
+ self.assertFalse(AidegenModuleInfo.is_target_module({}))
+ self.assertFalse(AidegenModuleInfo.is_target_module({'path': ''}))
+ self.assertFalse(AidegenModuleInfo.is_target_module({'class': ''}))
+ self.assertTrue(AidegenModuleInfo.is_target_module({'class': ['APPS']}))
+ self.assertTrue(
+ AidegenModuleInfo.is_target_module({'class': ['JAVA_LIBRARIES']}))
+ self.assertTrue(
+ AidegenModuleInfo.is_target_module({'class': ['ROBOLECTRIC']}))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/aidegen/lib/module_info_util.py b/aidegen/lib/module_info_util.py
index 322f3ec..3c61442 100644
--- a/aidegen/lib/module_info_util.py
+++ b/aidegen/lib/module_info_util.py
@@ -22,85 +22,88 @@
merge them into one dictionary and return the merged dictionary to its caller.
Example usage:
-merged_dict = generate_module_info_json(atest_module_info, project, verbose)
+merged_dict = generate_merged_module_info(atest_module_info, project, verbose)
"""
import glob
import json
import logging
import os
-import subprocess
import sys
from aidegen import constant
-from aidegen.lib.common_util import COLORED_INFO
-from aidegen.lib.common_util import time_logged
-from aidegen.lib.common_util import get_related_paths
+from aidegen.lib import common_util
from aidegen.lib import errors
+from atest import atest_utils
+from atest import constants
+
_BLUEPRINT_JSONFILE_NAME = 'module_bp_java_deps.json'
-_KEY_CLS = 'class'
-_KEY_PATH = 'path'
-_KEY_INS = 'installed'
-_KEY_DEP = 'dependencies'
-_KEY_SRCS = 'srcs'
-_MERGE_NEEDED_ITEMS = [_KEY_CLS, _KEY_PATH, _KEY_INS, _KEY_DEP, _KEY_SRCS]
+_MERGE_NEEDED_ITEMS = [
+ constant.KEY_CLASS,
+ constant.KEY_PATH,
+ constant.KEY_INSTALLED,
+ constant.KEY_DEPENDENCIES,
+ constant.KEY_SRCS,
+ constant.KEY_SRCJARS,
+ constant.KEY_CLASSES_JAR,
+ constant.KEY_TAG,
+ constant.KEY_COMPATIBILITY,
+ constant.KEY_AUTO_TEST_CONFIG,
+ constant.KEY_MODULE_NAME,
+ constant.KEY_TEST_CONFIG
+]
_INTELLIJ_PROJECT_FILE_EXT = '*.iml'
_LAUNCH_PROJECT_QUERY = (
'There exists an IntelliJ project file: %s. Do you want '
'to launch it (yes/No)?')
-_GENERATE_JSON_COMMAND = ('SOONG_COLLECT_JAVA_DEPS=false make nothing;'
- 'SOONG_COLLECT_JAVA_DEPS=true make nothing')
+
+_BUILD_BP_JSON_ENV_OFF = {'SOONG_COLLECT_JAVA_DEPS': 'false'}
+_BUILD_BP_JSON_ENV_ON = constants.ATEST_BUILD_ENV
-@time_logged
-def generate_module_info_json(module_info, projects, verbose, skip_build=False):
- """Generate a merged json dictionary.
-
- Change directory to ANDROID_ROOT_PATH before making _GENERATE_JSON_COMMAND
- to avoid command error: "make: *** No rule to make target 'nothing'. Stop."
- and change back to current directory after command completed.
+@common_util.time_logged
+def generate_merged_module_info(module_info, projects=None, verbose=False,
+ skip_build=False):
+ """Generate a merged dictionary.
Linked functions:
- _build_target(project, verbose)
+ _build_bp_info(module_info, project, verbose, skip_build)
_get_soong_build_json_dict()
- _merge_json(mk_dict, bp_dict)
+ _merge_dict(mk_dict, bp_dict)
Args:
module_info: A ModuleInfo instance contains data of module-info.json.
projects: A list of project names.
verbose: A boolean, if true displays full build output.
- skip_build: A boolean, if true skip building _BLUEPRINT_JSONFILE_NAME if
- it exists, otherwise build it.
+ skip_build: A boolean, if true skip building
+ get_blueprint_json_path() if it exists, otherwise
+ build it.
Returns:
- A tuple of Atest module info instance and a merged json dictionary.
+ A merged dictionary from module-info.json and module_bp_java_deps.json.
"""
- cwd = os.getcwd()
- os.chdir(constant.ANDROID_ROOT_PATH)
- _build_target([_GENERATE_JSON_COMMAND], projects[0], module_info, verbose,
- skip_build)
- os.chdir(cwd)
+ main_project = projects[0] if projects else None
+ _build_bp_info(module_info, main_project, verbose, skip_build)
bp_dict = _get_soong_build_json_dict()
- return _merge_json(module_info.name_to_module_info, bp_dict)
+ return _merge_dict(module_info.name_to_module_info, bp_dict)
-def _build_target(cmd, main_project, module_info, verbose, skip_build=False):
+def _build_bp_info(module_info, main_project=None, verbose=False,
+ skip_build=False):
"""Make nothing to generate module_bp_java_deps.json.
- We build without environment setting SOONG_COLLECT_JAVA_DEPS and then build
- with environment setting SOONG_COLLECT_JAVA_DEPS. In this way we can trigger
- the process of collecting dependencies and generating
- module_bp_java_deps.json.
+ Using atest build method with set env config SOONG_COLLECT_JAVA_DEPS=true to
+ build the target nothing. By this way to trigger the process of collecting
+ dependencies and generating module_bp_java_deps.json.
Args:
- cmd: A string list, build command.
- main_project: The main project name.
module_info: A ModuleInfo instance contains data of module-info.json.
+ main_project: The main project name.
verbose: A boolean, if true displays full build output.
- skip_build: A boolean, if true skip building _BLUEPRINT_JSONFILE_NAME if
- it exists, otherwise build it.
-
+ skip_build: A boolean, if true, skip building if
+ get_blueprint_json_path() file exists, otherwise
+ build it.
Build results:
1. Build successfully return.
2. Build failed:
@@ -110,31 +113,37 @@
a) If the answer is yes, return.
b) If the answer is not yes, sys.exit(1)
"""
- json_path = _get_blueprint_json_path()
+ json_path = common_util.get_blueprint_json_path()
original_json_mtime = None
if os.path.isfile(json_path):
if skip_build:
logging.info('%s file exists, skipping build.',
- _BLUEPRINT_JSONFILE_NAME)
+ common_util.get_blueprint_json_path())
return
original_json_mtime = os.path.getmtime(json_path)
- try:
- if verbose:
- full_env_vars = os.environ.copy()
- subprocess.check_call(
- cmd, stderr=subprocess.STDOUT, env=full_env_vars, shell=True)
- else:
- subprocess.check_call(cmd, shell=True)
- logging.info('Build successfully: %s.', cmd)
- except subprocess.CalledProcessError:
+
+ logging.warning('\nUse atest build method to generate blueprint json.')
+ # Force build system to always generate the blueprint json file by setting
+ # SOONG_COLLECT_JAVA_DEPS to false, then true.
+ build_with_off_cmd = atest_utils.build(['nothing'], verbose,
+ _BUILD_BP_JSON_ENV_OFF)
+ build_with_on_cmd = atest_utils.build(['nothing'], verbose,
+ _BUILD_BP_JSON_ENV_ON)
+
+ if build_with_off_cmd and build_with_on_cmd:
+ logging.info('\nGenerate blueprint json successfully.')
+ else:
if not _is_new_json_file_generated(json_path, original_json_mtime):
if os.path.isfile(json_path):
message = ('Generate new {0} failed, AIDEGen will proceed and '
'reuse the old {0}.'.format(json_path))
- print('\n{} {}\n'.format(COLORED_INFO('Warning:'), message))
+ print('\n{} {}\n'.format(common_util.COLORED_INFO('Warning:'),
+ message))
else:
- _, main_project_path = get_related_paths(module_info, main_project)
- _build_failed_handle(main_project_path)
+ if main_project:
+ _, main_project_path = common_util.get_related_paths(
+ module_info, main_project)
+ _build_failed_handle(main_project_path)
def _is_new_json_file_generated(json_path, original_file_mtime):
@@ -143,6 +152,9 @@
Args:
json_path: The path of the json file being to check.
original_file_mtime: the original file modified time.
+
+ Returns:
+ A boolean, True if the json_path file is new generated, otherwise False.
"""
if not original_file_mtime:
return os.path.isfile(json_path)
@@ -165,22 +177,22 @@
project_file = glob.glob(
os.path.join(main_project_path, _INTELLIJ_PROJECT_FILE_EXT))
if project_file:
- query = (_LAUNCH_PROJECT_QUERY) % project_file[0]
+ query = _LAUNCH_PROJECT_QUERY % project_file[0]
input_data = input(query)
if not input_data.lower() in ['yes', 'y']:
sys.exit(1)
else:
raise errors.BuildFailureError(
- 'Failed to generate %s.' % _get_blueprint_json_path())
+ 'Failed to generate %s.' % common_util.get_blueprint_json_path())
def _get_soong_build_json_dict():
"""Load a json file from path and convert it into a json dictionary.
Returns:
- A json dictionary.
+ A dictionary loaded from the blueprint json file.
"""
- json_path = _get_blueprint_json_path()
+ json_path = common_util.get_blueprint_json_path()
try:
with open(json_path) as jfile:
json_dict = json.load(jfile)
@@ -190,17 +202,10 @@
'%s does not exist, error: %s.' % (json_path, err))
-def _get_blueprint_json_path():
- """Assemble the path of blueprint json file.
-
- Returns:
- Blueprint json path.
- """
- return os.path.join(constant.SOONG_OUT_DIR_PATH, _BLUEPRINT_JSONFILE_NAME)
-
-
def _merge_module_keys(m_dict, b_dict):
- """Merge a module's json dictionary into another module's json dictionary.
+ """Merge a module's dictionary into another module's dictionary.
+
+ Merge b_dict module data into m_dict.
Args:
m_dict: The module dictionary is going to merge b_dict into.
@@ -211,13 +216,13 @@
def _copy_needed_items_from(mk_dict):
- """Shallow copy needed items from Make build system part json dictionary.
+ """Shallow copy needed items from Make build system module info dictionary.
Args:
- mk_dict: Make build system json dictionary is going to be copyed.
+ mk_dict: Make build system dictionary is going to be copied.
Returns:
- A merged json dictionary.
+ A merged dictionary.
"""
merged_dict = dict()
for module in mk_dict.keys():
@@ -228,22 +233,22 @@
return merged_dict
-def _merge_json(mk_dict, bp_dict):
- """Merge two json dictionaries.
+def _merge_dict(mk_dict, bp_dict):
+ """Merge two dictionaries.
Linked function:
_merge_module_keys(m_dict, b_dict)
Args:
- mk_dict: Make build system part json dictionary.
- bp_dict: Soong build system part json dictionary.
+ mk_dict: Make build system module info dictionary.
+ bp_dict: Soong build system module info dictionary.
Returns:
- A merged json dictionary.
+ A merged dictionary.
"""
merged_dict = _copy_needed_items_from(mk_dict)
for module in bp_dict.keys():
- if not module in merged_dict.keys():
+ if module not in merged_dict.keys():
merged_dict[module] = dict()
_merge_module_keys(merged_dict[module], bp_dict[module])
return merged_dict
diff --git a/aidegen/lib/module_info_util_unittest.py b/aidegen/lib/module_info_util_unittest.py
index 0cf7f6b..24260e1 100644
--- a/aidegen/lib/module_info_util_unittest.py
+++ b/aidegen/lib/module_info_util_unittest.py
@@ -17,17 +17,17 @@
"""Unittests for module_info_utils."""
import copy
-import os
-import subprocess
+import os.path
import unittest
from unittest import mock
-import aidegen.unittest_constants as uc
+from aidegen import unittest_constants
+from aidegen.lib import common_util
from aidegen.lib import errors
-from aidegen.lib import metrics
from aidegen.lib import module_info_util
-from atest import module_info
+from atest import atest_utils
+from atest import module_info
_TEST_CLASS_DICT = {'class': ['JAVA_LIBRARIES']}
_TEST_SRCS_BAR_DICT = {'srcs': ['Bar']}
@@ -41,7 +41,9 @@
'path': ['path_a'],
'installed': ['out/path_a/a.jar'],
'dependencies': ['Foo'],
- 'srcs': ['Bar']
+ 'srcs': ['Bar'],
+ 'compatibility_suites': ['null-suite'],
+ 'module_name': ['ltp_fstat03_64']
}
}
_TEST_MODULE_A_DICT_HAS_NONEED_ITEMS = {
@@ -129,24 +131,24 @@
module_info_util._copy_needed_items_from(
_TEST_MODULE_A_DICT_HAS_NONEED_ITEMS))
- @mock.patch('subprocess.check_call')
- @mock.patch('os.environ.copy')
- def test_build_target_normal(self, mock_copy, mock_check_call):
+ @mock.patch.object(os.path, 'getmtime')
+ @mock.patch.object(atest_utils, 'build')
+ @mock.patch('os.path.isfile')
+ def test_build_bp_info_normal(self, mock_isfile, mock_build, mock_time):
"""Test _build_target with verbose true and false."""
- mock_copy.return_value = ''
+ mock_isfile.return_value = True
amodule_info = module_info.ModuleInfo()
- cmd = [module_info_util._GENERATE_JSON_COMMAND]
- module_info_util._build_target(cmd, uc.TEST_MODULE, amodule_info, True)
- self.assertTrue(mock_copy.called)
- self.assertTrue(mock_check_call.called)
- mock_check_call.assert_called_with(
- cmd,
- stderr=subprocess.STDOUT,
- env=mock_copy.return_value,
- shell=True)
- module_info_util._build_target(cmd, uc.TEST_MODULE, amodule_info, False)
- self.assertTrue(mock_check_call.called)
- mock_check_call.assert_called_with(cmd, shell=True)
+ skip = True
+ mock_build.return_value = True
+ module_info_util._build_bp_info(amodule_info, unittest_constants.
+ TEST_MODULE, False, skip)
+ self.assertFalse(mock_build.called)
+ skip = False
+ mock_time.return_value = float()
+ module_info_util._build_bp_info(amodule_info, unittest_constants.
+ TEST_MODULE, False, skip)
+ self.assertTrue(mock_time.called)
+ self.assertEqual(mock_build.call_count, 2)
@mock.patch('os.path.getmtime')
@mock.patch('os.path.isfile')
@@ -173,19 +175,20 @@
module_info_util._is_new_json_file_generated(
jfile, original_file_mtime))
- @mock.patch.object(metrics, 'ends_asuite_metrics')
@mock.patch('builtins.input')
@mock.patch('glob.glob')
- def test_build_failed_handle(self, mock_glob, mock_input, _send_exit):
+ def test_build_failed_handle(self, mock_glob, mock_input):
"""Test _build_failed_handle with different situations."""
mock_glob.return_value = ['project/file.iml']
mock_input.return_value = 'N'
with self.assertRaises(SystemExit) as cm:
- module_info_util._build_failed_handle(uc.TEST_MODULE)
+ module_info_util._build_failed_handle(
+ unittest_constants.TEST_MODULE)
self.assertEqual(cm.exception.code, 1)
mock_glob.return_value = []
with self.assertRaises(errors.BuildFailureError):
- module_info_util._build_failed_handle(uc.TEST_MODULE)
+ module_info_util._build_failed_handle(
+ unittest_constants.TEST_MODULE)
@mock.patch('builtins.open')
def test_get_soong_build_json_dict_failed(self, mock_open):
@@ -195,28 +198,22 @@
module_info_util._get_soong_build_json_dict()
@mock.patch('aidegen.lib.module_info_util._build_failed_handle')
+ @mock.patch.object(common_util, 'get_related_paths')
@mock.patch('aidegen.lib.module_info_util._is_new_json_file_generated')
- @mock.patch('subprocess.check_call')
- def test_build_target(self, mock_call, mock_new, mock_handle):
- """Test _build_target with different arguments."""
- cmd = [module_info_util._GENERATE_JSON_COMMAND]
- main_project = ''
+ @mock.patch.object(atest_utils, 'build')
+ def test_build_bp_info(self, mock_build, mock_new, mock_path, mock_handle):
+ """Test _build_bp_info with different arguments."""
+ main_project = 'Settings'
amodule_info = {}
verbose = False
- module_info_util._build_target(cmd, main_project, amodule_info, verbose)
- mock_call.assert_called_with(cmd, shell=True)
- verbose = True
- full_env_vars = os.environ.copy()
- module_info_util._build_target(cmd, main_project, amodule_info, verbose)
- mock_call.assert_called_with(cmd, stderr=subprocess.STDOUT,
- env=full_env_vars, shell=True)
- mock_call.side_effect = subprocess.CalledProcessError(1, '')
+ mock_build.return_value = False
mock_new.return_value = False
- module_info_util._build_target(cmd, main_project, amodule_info, verbose)
+ module_info_util._build_bp_info(amodule_info, main_project, verbose)
self.assertTrue(mock_new.called)
self.assertFalse(mock_handle.called)
mock_new.return_value = True
- module_info_util._build_target(cmd, main_project, amodule_info, verbose)
+ mock_path.return_value = None, 'packages/apps/Settings'
+ module_info_util._build_bp_info(amodule_info, main_project, verbose)
self.assertTrue(mock_new.called)
self.assertTrue(mock_handle.called)
diff --git a/aidegen/lib/project_config.py b/aidegen/lib/project_config.py
new file mode 100644
index 0000000..7bf4ecf
--- /dev/null
+++ b/aidegen/lib/project_config.py
@@ -0,0 +1,62 @@
+#!/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.
+
+"""Project config class."""
+
+from aidegen import constant
+from aidegen.lib import common_util
+
+SKIP_BUILD_INFO = ('If you are sure the related modules and dependencies have '
+ 'been already built, please try to use command {} to skip '
+ 'the building process.')
+_SKIP_BUILD_CMD = 'aidegen {} -s'
+_SKIP_BUILD_WARN = (
+ 'You choose "--skip-build". Skip building jar and module might increase '
+ 'the risk of the absence of some jar or R/AIDL/logtags java files and '
+ 'cause the red lines to appear in IDE tool.')
+
+
+class ProjectConfig:
+ """Class manages AIDEGen's configurations.
+
+ Attributes:
+ ide_name: The IDE name which user prefer to launch.
+ is_launch_ide: A boolean for launching IDE in the end of AIDEGen.
+ depth: The depth of module referenced by source.
+ full_repo: A boolean decides import whole Android source repo.
+ is_skip_build: A boolean decides skipping building jars or modules.
+ targets: A string list with Android module names or paths.
+ """
+
+ def __init__(self, args):
+ self.ide_name = constant.IDE_NAME_DICT[args.ide[0]]
+ self.is_launch_ide = not args.no_launch
+ self.depth = args.depth
+ self.full_repo = args.android_tree
+ self.is_skip_build = args.skip_build
+ self.targets = args.targets
+ self._show_skip_build_msg()
+
+ def _show_skip_build_msg(self):
+ """Display different messages if users skip building targets or not."""
+ if self.is_skip_build:
+ print('\n{} {}\n'.format(
+ common_util.COLORED_INFO('Warning:'), _SKIP_BUILD_WARN))
+ else:
+ msg = SKIP_BUILD_INFO.format(
+ common_util.COLORED_INFO(
+ _SKIP_BUILD_CMD.format(' '.join(self.targets))))
+ print('\n{} {}\n'.format(common_util.COLORED_INFO('INFO:'), msg))
diff --git a/aidegen/lib/project_file_gen.py b/aidegen/lib/project_file_gen.py
index 2535945..b442255 100644
--- a/aidegen/lib/project_file_gen.py
+++ b/aidegen/lib/project_file_gen.py
@@ -16,11 +16,9 @@
"""It is an AIDEGen sub task : generate the project files.
-This module generate IDE project files from templates.
-
- Typical usage example:
-
- generate_ide_project_file(project_info)
+ Usage example:
+ projects: A list of ProjectInfo instances.
+ ProjectFileGenerator.generate_ide_project_file(projects)
"""
import logging
@@ -38,8 +36,12 @@
</facet>'''
_SOURCE_FOLDER = (' <sourceFolder url='
'"file://%s" isTestSource="%s" />\n')
+_EXCLUDE_ITEM = ' <excludeFolder url="file://%s" />\n'
_CONTENT_URL = ' <content url="file://%s">\n'
_END_CONTENT = ' </content>\n'
+_SRCJAR_URL = ('%s<content url="jar://{SRCJAR}">\n'
+ '%s<sourceFolder url="jar://{SRCJAR}" isTestSource="False" />\n'
+ '%s</content>') % (' ' * 8, ' ' * 12, ' ' * 8)
_ORDER_ENTRY = (' <orderEntry type="module-library" exported="">'
'<library><CLASSES><root url="jar://%s!/" /></CLASSES>'
'<JAVADOC /><SOURCES /></library></orderEntry>\n')
@@ -47,24 +49,21 @@
'module-name="%s" />')
_MODULE_SECTION = (' <module fileurl="file:///$PROJECT_DIR$/%s.iml"'
' filepath="$PROJECT_DIR$/%s.iml" />')
-_SUB_MODULES_SECTION = (' <module fileurl="file:///%s" '
- 'filepath="%s" />')
+_SUB_MODULES_SECTION = (' <module fileurl="file:///{IML}" '
+ 'filepath="{IML}" />')
_VCS_SECTION = ' <mapping directory="%s" vcs="Git" />'
_FACET_TOKEN = '@FACETS@'
_SOURCE_TOKEN = '@SOURCES@'
+_SRCJAR_TOKEN = '@SRCJAR@'
_MODULE_DEP_TOKEN = '@MODULE_DEPENDENCIES@'
_MODULE_TOKEN = '@MODULES@'
+_ENABLE_DEBUGGER_MODULE_TOKEN = '@ENABLE_DEBUGGER_MODULE@'
_VCS_TOKEN = '@VCS@'
_JAVA_FILE_PATTERN = '%s/*.java'
-_ROOT_DIR = constant.AIDEGEN_ROOT_PATH
-_IDEA_DIR = os.path.join(_ROOT_DIR, 'templates/idea')
-_TEMPLATE_IML_PATH = os.path.join(_ROOT_DIR, 'templates/module-template.iml')
+_IDEA_DIR = os.path.join(common_util.get_aidegen_root_dir(), 'templates/idea')
_IDEA_FOLDER = '.idea'
_MODULES_XML = 'modules.xml'
_VCS_XML = 'vcs.xml'
-_TEMPLATE_MODULES_PATH = os.path.join(_IDEA_DIR, _MODULES_XML)
-_TEMPLATE_VCS_PATH = os.path.join(_IDEA_DIR, _VCS_XML)
-_DEPENDENCIES = 'dependencies'
_DEPENDENCIES_IML = 'dependencies.iml'
_COPYRIGHT_FOLDER = 'copyright'
_CODE_STYLE_FOLDER = 'codeStyles'
@@ -74,350 +73,509 @@
_IML_EXTENSION = '.iml'
_FRAMEWORK_JAR = os.sep + 'framework.jar'
_HIGH_PRIORITY_JARS = [_FRAMEWORK_JAR]
+_EXCLUDE_FOLDERS = ['.idea', '.repo', 'art', 'bionic', 'bootable', 'build',
+ 'dalvik', 'developers', 'device', 'hardware', 'kernel',
+ 'libnativehelper', 'pdk', 'prebuilts', 'sdk', 'system',
+ 'toolchain', 'tools', 'vendor', 'out']
_GIT_FOLDER_NAME = '.git'
# Support gitignore by symbolic link to aidegen/data/gitignore_template.
_GITIGNORE_FILE_NAME = '.gitignore'
_GITIGNORE_REL_PATH = 'tools/asuite/aidegen/data/gitignore_template'
-_GITIGNORE_ABS_PATH = os.path.join(constant.ANDROID_ROOT_PATH,
+_GITIGNORE_ABS_PATH = os.path.join(common_util.get_android_root_dir(),
_GITIGNORE_REL_PATH)
# Support code style by symbolic link to aidegen/data/AndroidStyle_aidegen.xml.
_CODE_STYLE_REL_PATH = 'tools/asuite/aidegen/data/AndroidStyle_aidegen.xml'
-_CODE_STYLE_SRC_PATH = os.path.join(constant.ANDROID_ROOT_PATH,
+_CODE_STYLE_SRC_PATH = os.path.join(common_util.get_android_root_dir(),
_CODE_STYLE_REL_PATH)
-_ECLIP_SRC_ENTRY = '<classpathentry exported="true" kind="src" path="{}"/>\n'
-_ECLIP_LIB_ENTRY = '<classpathentry exported="true" kind="lib" path="{}"/>\n'
-_ECLIP_TEMPLATE_PATH = os.path.join(_ROOT_DIR, 'templates/eclipse/eclipse.xml')
-_ECLIP_EXTENSION = '.classpath'
-_ECLIP_SRC_TOKEN = '@SRC@'
-_ECLIP_LIB_TOKEN = '@LIB@'
-_ECLIP_PROJECT_PATH = os.path.join(_ROOT_DIR, 'templates/eclipse/project.xml')
-_ECLIP_PROJECT_NAME_TOKEN = '@PROJECTNAME@'
-_ECLIP_PROJECT_EXTENSION = '.project'
-# b/121256503: Prevent duplicated iml names from breaking IDEA.
-# Use a map to cache in-using(already used) iml project file names.
-_USED_NAME_CACHE = dict()
+class ProjectFileGenerator:
+ """Project file generator.
+ Class attributes:
+ _USED_NAME_CACHE: A dict to cache already used iml project file names
+ and prevent duplicated iml names from breaking IDEA.
-def get_unique_iml_name(abs_module_path):
- """Create a unique iml name if needed.
-
- If the name of last sub folder is used already, prefixing it with prior sub
- folder names as a candidate name. If finally, it's unique, storing in
- _USED_NAME_CACHE as: { abs_module_path:unique_name }. The cts case and UX of
- IDE view are the main reasons why using module path strategy but not name of
- module directly. Following is the detailed strategy:
- 1. While loop composes a sensible and shorter name, by checking unique to
- finish the loop and finally add to cache.
- Take ['cts', 'tests', 'app', 'ui'] an example, if 'ui' isn't occupied,
- use it, else try 'cts_ui', then 'cts_app_ui', the worst case is whole
- three candidate names are occupied already.
- 2. 'Else' for that while stands for no suitable name generated, so trying
- 'cts_tests_app_ui' directly. If it's still non unique, e.g., module path
- cts/xxx/tests/app/ui occupied that name already, appending increasing
- sequence number to get a unique name.
-
- Args:
- abs_module_path: Full module path string.
-
- Return:
- String: A unique iml name.
+ Attributes:
+ project_info: A instance of ProjectInfo.
"""
- if abs_module_path in _USED_NAME_CACHE:
- return _USED_NAME_CACHE[abs_module_path]
+ # b/121256503: Prevent duplicated iml names from breaking IDEA.
+ # Use a map to cache in-using(already used) iml project file names.
+ _USED_NAME_CACHE = dict()
- uniq_name = abs_module_path.strip(os.sep).split(os.sep)[-1]
- if any(uniq_name == name for name in _USED_NAME_CACHE.values()):
- parent_path = os.path.relpath(abs_module_path,
- constant.ANDROID_ROOT_PATH)
- sub_folders = parent_path.split(os.sep)
- zero_base_index = len(sub_folders) - 1
- # Start compose a sensible, shorter and unique name.
- while zero_base_index > 0:
- uniq_name = '_'.join(
- [sub_folders[0], '_'.join(sub_folders[zero_base_index:])])
- zero_base_index = zero_base_index - 1
- if uniq_name not in _USED_NAME_CACHE.values():
- break
- else:
- # b/133393638: To handle several corner cases.
- uniq_name_base = parent_path.strip(os.sep).replace(os.sep, '_')
- i = 0
- uniq_name = uniq_name_base
- while uniq_name in _USED_NAME_CACHE.values():
- i = i + 1
- uniq_name = '_'.join([uniq_name_base, str(i)])
- _USED_NAME_CACHE[abs_module_path] = uniq_name
- logging.debug('Unique name for module path of %s is %s.', abs_module_path,
- uniq_name)
- return uniq_name
+ def __init__(self, project_info):
+ """ProjectFileGenerator initialize.
+ Args:
+ project_info: A instance of ProjectInfo.
+ """
+ self.project_info = project_info
-def _generate_intellij_project_file(project_info, iml_path_list=None):
- """Generates IntelliJ project file.
+ @classmethod
+ def get_unique_iml_name(cls, abs_module_path):
+ """Create a unique iml name if needed.
- Args:
- project_info: ProjectInfo instance.
- iml_path_list: An optional list of submodule's iml paths, default None.
- """
- source_dict = dict.fromkeys(
- list(project_info.source_path['source_folder_path']), False)
- source_dict.update(
- dict.fromkeys(list(project_info.source_path['test_folder_path']), True))
- project_info.iml_path, _ = _generate_iml(
- constant.ANDROID_ROOT_PATH, project_info.project_absolute_path,
- source_dict, list(project_info.source_path['jar_path']),
- project_info.project_relative_path)
- _generate_modules_xml(project_info.project_absolute_path, iml_path_list)
- project_info.git_path = _generate_vcs_xml(
- project_info.project_absolute_path)
- _copy_constant_project_files(project_info.project_absolute_path)
+ If the name of last sub folder is used already, prefixing it with prior
+ sub folder names as a candidate name. If finally, it's unique, storing
+ in _USED_NAME_CACHE as: { abs_module_path:unique_name }. The cts case
+ and UX of IDE view are the main reasons why using module path strategy
+ but not name of module directly. Following is the detailed strategy:
+ 1. While loop composes a sensible and shorter name, by checking unique
+ to finish the loop and finally add to cache.
+ Take ['cts', 'tests', 'app', 'ui'] an example, if 'ui' isn't
+ occupied, use it, else try 'cts_ui', then 'cts_app_ui', the worst
+ case is whole three candidate names are occupied already.
+ 2. 'Else' for that while stands for no suitable name generated, so
+ trying 'cts_tests_app_ui' directly. If it's still non unique, e.g.,
+ module path cts/xxx/tests/app/ui occupied that name already,
+ appending increasing sequence number to get a unique name.
+ Args:
+ abs_module_path: The absolute module path string.
-def generate_ide_project_files(projects):
- """Generate IDE project files by a list of ProjectInfo instances.
+ Return:
+ String: A unique iml name.
+ """
+ if abs_module_path in cls._USED_NAME_CACHE:
+ return cls._USED_NAME_CACHE[abs_module_path]
- For multiple modules case, we call _generate_intellij_project_file to
- generate iml file for submodules first and pass submodules' iml file paths
- as an argument to function _generate_intellij_project_file when we generate
- main module.iml file. In this way, we can add submodules' dependencies iml
- and their own iml file paths to main module's module.xml.
+ uniq_name = abs_module_path.strip(os.sep).split(os.sep)[-1]
+ if any(uniq_name == name for name in cls._USED_NAME_CACHE.values()):
+ parent_path = os.path.relpath(abs_module_path,
+ common_util.get_android_root_dir())
+ sub_folders = parent_path.split(os.sep)
+ zero_base_index = len(sub_folders) - 1
+ # Start compose a sensible, shorter and unique name.
+ while zero_base_index > 0:
+ uniq_name = '_'.join(
+ [sub_folders[0], '_'.join(sub_folders[zero_base_index:])])
+ zero_base_index = zero_base_index - 1
+ if uniq_name not in cls._USED_NAME_CACHE.values():
+ break
+ else:
+ # b/133393638: To handle several corner cases.
+ uniq_name_base = parent_path.strip(os.sep).replace(os.sep, '_')
+ i = 0
+ uniq_name = uniq_name_base
+ while uniq_name in cls._USED_NAME_CACHE.values():
+ i = i + 1
+ uniq_name = '_'.join([uniq_name_base, str(i)])
+ cls._USED_NAME_CACHE[abs_module_path] = uniq_name
+ logging.debug('Unique name for module path of %s is %s.',
+ abs_module_path, uniq_name)
+ return uniq_name
- Args:
- projects: A list of ProjectInfo instances.
- """
- # Initialization
- _USED_NAME_CACHE.clear()
+ def _generate_source_section(self, sect_name, is_test):
+ """Generate specific section of the project file.
- for project in projects[1:]:
- _generate_intellij_project_file(project)
- iml_paths = [project.iml_path for project in projects[1:]]
- _generate_intellij_project_file(projects[0], iml_paths)
- _merge_project_vcs_xmls(projects)
+ Args:
+ sect_name: The section name, e.g. source_folder_path is for source
+ folder section.
+ is_test: A boolean, True if it's the test section else False.
+ Returns:
+ A dict contains the source folder's contents of project file.
+ """
+ return dict.fromkeys(
+ list(self.project_info.source_path[sect_name]), is_test)
-def _generate_eclipse_project_file(project_info):
- """Generates Eclipse project file.
+ def generate_intellij_project_file(self, iml_path_list=None):
+ """Generates IntelliJ project file.
- Args:
- project_info: ProjectInfo instance.
- """
- module_path = project_info.project_absolute_path
- module_name = get_unique_iml_name(module_path)
- _generate_eclipse_project(module_name, module_path)
- source_dict = dict.fromkeys(
- list(project_info.source_path['source_folder_path']), False)
- source_dict.update(
- dict.fromkeys(list(project_info.source_path['test_folder_path']), True))
- project_info.iml_path = _generate_classpath(
- project_info.project_absolute_path, list(sorted(source_dict)),
- list(project_info.source_path['jar_path']))
+ Args:
+ iml_path_list: An optional list of submodule's iml paths, the
+ default value is None.
+ """
+ source_dict = self._generate_source_section('source_folder_path', False)
+ source_dict.update(
+ self._generate_source_section('test_folder_path', True))
+ self.project_info.iml_path, _ = self._generate_iml(source_dict)
+ self.project_info.git_path = self._get_project_git_path()
+ if self.project_info.is_main_project:
+ self._generate_modules_xml(iml_path_list)
+ self._copy_constant_project_files()
+ @classmethod
+ def generate_ide_project_files(cls, projects):
+ """Generate IDE project files by a list of ProjectInfo instances.
-def generate_eclipse_project_files(projects):
- """Generate Eclipse project files by a list of ProjectInfo instances.
+ For multiple modules case, we call _generate_intellij_project_file to
+ generate iml file for submodules first and pass submodules' iml file
+ paths as an argument to function _generate_intellij_project_file when we
+ generate main module.iml file. In this way, we can add submodules'
+ dependencies iml and their own iml file paths to main module's
+ module.xml.
- Args:
- projects: A list of ProjectInfo instances.
- """
- for project in projects:
- _generate_eclipse_project_file(project)
+ Args:
+ projects: A list of ProjectInfo instances.
+ """
+ # Initialization
+ cls._USED_NAME_CACHE.clear()
+ _merge_all_shared_source_paths(projects)
+ for project in projects[1:]:
+ ProjectFileGenerator(project).generate_intellij_project_file()
+ iml_paths = [project.iml_path for project in projects[1:]]
+ ProjectFileGenerator(
+ projects[0]).generate_intellij_project_file(iml_paths)
+ _merge_project_vcs_xmls(projects)
+ def _copy_constant_project_files(self):
+ """Copy project files to target path with error handling.
-def _read_file_content(path):
- """Read file's content.
+ This function would copy compiler.xml, misc.xml, codeStyles folder and
+ copyright folder to target folder. Since these files aren't mandatory in
+ IntelliJ, it only logs when an IOError occurred.
+ """
+ target_path = self.project_info.project_absolute_path
+ try:
+ self._copy_to_idea_folder(target_path, _COPYRIGHT_FOLDER)
+ self._copy_to_idea_folder(target_path, _CODE_STYLE_FOLDER)
+ code_style_target_path = os.path.join(
+ target_path, _IDEA_FOLDER, _CODE_STYLE_FOLDER, 'Project.xml')
+ # Base on current working directory to prepare the relevant location
+ # of the symbolic link file, and base on the symlink file location
+ # to prepare the relevant code style source path.
+ rel_target = os.path.relpath(code_style_target_path, os.getcwd())
+ rel_source = os.path.relpath(
+ _CODE_STYLE_SRC_PATH, os.path.dirname(code_style_target_path))
+ logging.debug('Relative target symlink path: %s.', rel_target)
+ logging.debug('Relative code style source path: %s.', rel_source)
+ os.symlink(rel_source, rel_target)
+ # Create .gitignore if it doesn't exist.
+ _generate_git_ignore(target_path)
+ shutil.copy(
+ os.path.join(_IDEA_DIR, _COMPILE_XML),
+ os.path.join(target_path, _IDEA_FOLDER, _COMPILE_XML))
+ shutil.copy(
+ os.path.join(_IDEA_DIR, _MISC_XML),
+ os.path.join(target_path, _IDEA_FOLDER, _MISC_XML))
+ except (IOError, SystemError) as err:
+ logging.warning('%s can\'t copy the project files\n %s',
+ target_path, err)
- Args:
- path: Path of input file.
+ @staticmethod
+ def _copy_to_idea_folder(target_path, folder_name):
+ """Copy folder to project .idea path.
- Returns:
- String: Content of the file.
- """
- with open(path) as template:
- return template.read()
+ Args:
+ target_path: Path of target folder.
+ folder_name: Name of target folder.
+ """
+ abs_target_path = os.path.join(target_path, _IDEA_FOLDER, folder_name)
+ # Existing folder needs to be removed first, otherwise it will raise
+ # IOError.
+ if os.path.exists(abs_target_path):
+ shutil.rmtree(abs_target_path)
+ shutil.copytree(os.path.join(_IDEA_DIR, folder_name), abs_target_path)
+ def _handle_facet(self, content):
+ """Handle facet part of iml.
-def _file_generate(path, content):
- """Generate file from content.
+ If the module is an Android app, which contains AndroidManifest.xml, it
+ should have a facet of android, otherwise we don't need facet in iml.
- Args:
- path: Path of target file.
- content: String content of file.
- """
- if not os.path.exists(os.path.dirname(path)):
- os.makedirs(os.path.dirname(path))
- with open(path, 'w') as target:
- target.write(content)
+ Args:
+ content: String content of iml.
+ Returns:
+ String: Content with facet handled.
+ """
+ facet = ''
+ facet_path = self.project_info.project_absolute_path
+ if os.path.isfile(os.path.join(facet_path, _ANDROID_MANIFEST)):
+ facet = _FACET_SECTION
+ return content.replace(_FACET_TOKEN, facet)
-def _copy_constant_project_files(target_path):
- """Copy project files to target path with error handling.
+ @staticmethod
+ def _handle_module_dependency(content, jar_dependencies):
+ """Handle module dependency part of iml.
- This function would copy compiler.xml, misc.xml, codeStyles folder and
- copyright folder to target folder. Since these files aren't mandatory in
- IntelliJ, it only logs when an IOError occurred.
+ Args:
+ content: String content of iml.
+ jar_dependencies: List of the jar path.
- Args:
- target_path: A folder path to copy content to.
- """
- try:
- _copy_to_idea_folder(target_path, _COPYRIGHT_FOLDER)
- _copy_to_idea_folder(target_path, _CODE_STYLE_FOLDER)
- code_style_target_path = os.path.join(target_path, _IDEA_FOLDER,
- _CODE_STYLE_FOLDER, 'Project.xml')
- # Base on current working directory to prepare the relevant location
- # of the symbolic link file, and base on the symlink file location to
- # prepare the relevant code style source path.
- rel_target = os.path.relpath(code_style_target_path, os.getcwd())
- rel_source = os.path.relpath(_CODE_STYLE_SRC_PATH,
- os.path.dirname(code_style_target_path))
- logging.debug('Relative target symlink path: %s.', rel_target)
- logging.debug('Relative code style source path: %s.', rel_source)
- os.symlink(rel_source, rel_target)
- # Create .gitignore if it doesn't exist.
- _generate_git_ignore(target_path)
- shutil.copy(
- os.path.join(_IDEA_DIR, _COMPILE_XML),
- os.path.join(target_path, _IDEA_FOLDER, _COMPILE_XML))
- shutil.copy(
- os.path.join(_IDEA_DIR, _MISC_XML),
- os.path.join(target_path, _IDEA_FOLDER, _MISC_XML))
- except IOError as err:
- logging.warning('%s can\'t copy the project files\n %s', target_path,
- err)
+ Returns:
+ String: Content with module dependency handled.
+ """
+ root_path = common_util.get_android_root_dir()
+ module_library = ''
+ dependencies = []
+ # Reorder deps in the iml generated by IntelliJ by inserting priority
+ # jars.
+ for jar_path in jar_dependencies:
+ if any((jar_path.endswith(high_priority_jar))
+ for high_priority_jar in _HIGH_PRIORITY_JARS):
+ module_library += _ORDER_ENTRY % os.path.join(
+ root_path, jar_path)
+ else:
+ dependencies.append(jar_path)
-
-def _copy_to_idea_folder(target_path, folder_name):
- """Copy folder to project .idea path.
-
- Args:
- target_path: Path of target folder.
- folder_name: Name of target folder.
- """
- target_folder_path = os.path.join(target_path, _IDEA_FOLDER, folder_name)
- # Existing folder needs to be removed first, otherwise it will raise
- # IOError.
- if os.path.exists(target_folder_path):
- shutil.rmtree(target_folder_path)
- shutil.copytree(os.path.join(_IDEA_DIR, folder_name), target_folder_path)
-
-
-def _handle_facet(content, path):
- """Handle facet part of iml.
-
- If the module is an Android app, which contains AndroidManifest.xml, it
- should have a facet of android, otherwise we don't need facet in iml.
-
- Args:
- content: String content of iml.
- path: Path of the module.
-
- Returns:
- String: Content with facet handled.
- """
- facet = ''
- if os.path.isfile(os.path.join(path, _ANDROID_MANIFEST)):
- facet = _FACET_SECTION
- return content.replace(_FACET_TOKEN, facet)
-
-
-def _handle_module_dependency(root_path, content, jar_dependencies):
- """Handle module dependency part of iml.
-
- Args:
- root_path: Android source tree root path.
- content: String content of iml.
- jar_dependencies: List of the jar path.
-
- Returns:
- String: Content with module dependency handled.
- """
- module_library = ''
- dependencies = []
- # Reorder deps in the iml generated by IntelliJ by inserting priority jars.
- for jar_path in jar_dependencies:
- if any((jar_path.endswith(high_priority_jar))
- for high_priority_jar in _HIGH_PRIORITY_JARS):
+ # IntelliJ indexes jars as dependencies from iml by the ascending order.
+ # Without sorting, the order of jar list changes everytime. Sort the jar
+ # list to keep the jar dependencies in consistency. It also can help us
+ # to discover potential issues like duplicated classes.
+ for jar_path in sorted(dependencies):
module_library += _ORDER_ENTRY % os.path.join(root_path, jar_path)
- else:
- dependencies.append(jar_path)
+ return content.replace(_MODULE_DEP_TOKEN, module_library)
- # IntelliJ indexes jars as dependencies from iml by the ascending order.
- # Without sorting, the order of jar list changes everytime. Sort the jar
- # list to keep the jar dependencies in consistency. It also can help us to
- # discover potential issues like duplicated classes.
- for jar_path in sorted(dependencies):
- module_library += _ORDER_ENTRY % os.path.join(root_path, jar_path)
- return content.replace(_MODULE_DEP_TOKEN, module_library)
+ def _is_project_relative_source(self, source):
+ """Check if the relative path of a file is a source relative path.
+ Check if the file path starts with the relative path or the relative is
+ an Android source tree root path.
-def _is_project_relative_source(source, relative_path):
- """Check if the relative path of a file is a source relative path.
+ Args:
+ source: The file path to be checked.
- Check if the file path starts with the relative path or the relative is an
- Android source tree root path.
+ Returns:
+ True if the file is a source relative path, otherwise False.
+ """
+ relative_path = self.project_info.project_relative_path
+ abs_path = common_util.get_abs_path(relative_path)
+ if common_util.is_android_root(abs_path):
+ return True
+ if _is_source_under_relative_path(source, relative_path):
+ return True
+ return False
- Args:
- source: The file path to be checked.
- relative_path: The relative path to be checked.
+ def _handle_source_folder(self, content, source_dict, is_module):
+ """Handle source folder part of iml.
- Returns:
- True if the file is a source relative path, otherwise False.
- """
- abs_path = common_util.get_abs_path(relative_path)
- if common_util.is_android_root(abs_path):
- return True
- if _is_source_under_relative_path(source, relative_path):
- return True
- return False
+ It would make the source folder group by content.
+ e.g.
+ <content url="file://$MODULE_DIR$/a">
+ <sourceFolder url="file://$MODULE_DIR$/a/b" isTestSource="False"/>
+ <sourceFolder url="file://$MODULE_DIR$/a/test" isTestSource="True"/>
+ <sourceFolder url="file://$MODULE_DIR$/a/d/e" isTestSource="False"/>
+ </content>
+ Args:
+ content: String content of iml.
+ source_dict: A dictionary of sources path with a flag to identify
+ the path is test or source folder in IntelliJ.
+ e.g.
+ {'path_a': True, 'path_b': False}
+ is_module: True if it is module iml, otherwise it is dependencies
+ iml.
-def _handle_source_folder(root_path, content, source_dict, is_module,
- relative_path):
- """Handle source folder part of iml.
+ Returns:
+ String: Content with source folder handled.
+ """
+ root_path = common_util.get_android_root_dir()
+ relative_path = self.project_info.project_relative_path
- It would make the source folder group by content.
- e.g.
- <content url="file://$MODULE_DIR$/a">
- <sourceFolder url="file://$MODULE_DIR$/a/b" isTestSource="False" />
- <sourceFolder url="file://$MODULE_DIR$/a/test" isTestSource="True" />
- <sourceFolder url="file://$MODULE_DIR$/a/d/e" isTestSource="False" />
- </content>
-
- Args:
- root_path: Android source tree root path.
- content: String content of iml.
- source_dict: A dictionary of sources path with a flag to identify the
- path is test or source folder in IntelliJ.
- e.g.
- {'path_a': True, 'path_b': False}
- is_module: True if it is module iml, otherwise it is dependencies iml.
- relative_path: Relative path of the module.
-
- Returns:
- String: Content with source folder handled.
- """
- source_list = list(source_dict.keys())
- source_list.sort()
- src_builder = []
- if is_module:
- # Set the content url to module's path since it's the iml of target
- # project which only has it's sub-folders in source_list.
- src_builder.append(
- _CONTENT_URL % os.path.join(root_path, relative_path))
- for path, is_test_flag in sorted(source_dict.items()):
- if _is_project_relative_source(path, relative_path):
- src_builder.append(_SOURCE_FOLDER % (os.path.join(
- root_path, path), is_test_flag))
- src_builder.append(_END_CONTENT)
- else:
- for path, is_test_flag in sorted(source_dict.items()):
- path = os.path.join(root_path, path)
- src_builder.append(_CONTENT_URL % path)
- src_builder.append(_SOURCE_FOLDER % (path, is_test_flag))
+ src_builder = []
+ if is_module:
+ # Set the content url to module's path since it's the iml of target
+ # project which only has it's sub-folders in source_list.
+ src_builder.append(
+ _CONTENT_URL % os.path.join(root_path, relative_path))
+ for path, is_test_flag in sorted(source_dict.items()):
+ if self._is_project_relative_source(path):
+ src_builder.append(_SOURCE_FOLDER % (os.path.join(
+ root_path, path), is_test_flag))
+ # If relative_path empty, it is Android root. When handling root
+ # module, we add the exclude folders to speed up indexing time.
+ if not relative_path:
+ src_builder.extend(_get_exclude_content(root_path))
src_builder.append(_END_CONTENT)
- return content.replace(_SOURCE_TOKEN, ''.join(src_builder))
+ else:
+ for path, is_test_flag in sorted(source_dict.items()):
+ path = os.path.join(root_path, path)
+ src_builder.append(_CONTENT_URL % path)
+ src_builder.append(_SOURCE_FOLDER % (path, is_test_flag))
+ src_builder.append(_END_CONTENT)
+ return content.replace(_SOURCE_TOKEN, ''.join(src_builder))
+
+ @staticmethod
+ def _handle_srcjar_folder(content, srcjar_paths=None):
+ """Handle the aapt2.srcjar and R.jar content for iml.
+
+ Example for setting the aapt2.srcjar or R.jar as a source folder in
+ IntelliJ.
+ e.g.
+ <content url="jar://$MODULE_DIR$/aapt2.srcjar!/">
+ <sourceFolder url="jar://$MODULE_DIR$/aapt2.srcjar!/"
+ isTestSource="False"/>
+ </content>
+ <content url="jar://$MODULE_DIR$/R.jar!/">
+ <sourceFolder url="jar://$MODULE_DIR$/R.jar!/"
+ isTestSource="False"/>
+ </content>
+
+ Args:
+ content: String content of iml.
+ srcjar_paths: A set of srcjar paths, default value is None.
+
+ Returns:
+ String: Content with srcjar folder handled.
+ """
+ srcjar_urls = []
+ if srcjar_paths:
+ for srcjar_dir in srcjar_paths:
+ srcjar_urls.append(_SRCJAR_URL.format(SRCJAR=os.path.join(
+ common_util.get_android_root_dir(), srcjar_dir)))
+ if srcjar_urls:
+ return content.replace(_SRCJAR_TOKEN, '\n'.join(srcjar_urls))
+ return content.replace(_SRCJAR_TOKEN + '\n', '')
+
+ # pylint: disable=too-many-locals
+ def _generate_iml(self, source_dict):
+ """Generate iml file.
+
+ Args:
+ source_dict: A dictionary of sources path with a flag to distinguish
+ the path is test or source folder in IntelliJ.
+ e.g.
+ {'path_a': True, 'path_b': False}
+
+ Returns:
+ String: The absolute paths of module iml and dependencies iml.
+ """
+ module_path = self.project_info.project_absolute_path
+ jar_dependencies = list(self.project_info.source_path['jar_path'])
+ # Separate module and dependencies source folder
+ project_source_dict = {}
+ for source in list(source_dict):
+ if self._is_project_relative_source(source):
+ is_test = source_dict.get(source)
+ source_dict.pop(source)
+ project_source_dict.update({source: is_test})
+
+ # Generate module iml.
+ module_content = self._handle_facet(constant.FILE_IML)
+ module_content = self._handle_source_folder(module_content,
+ project_source_dict, True)
+ module_content = self._handle_srcjar_folder(module_content)
+ # b/121256503: Prevent duplicated iml names from breaking IDEA.
+ module_name = self.get_unique_iml_name(module_path)
+
+ module_iml_path = os.path.join(module_path,
+ module_name + _IML_EXTENSION)
+
+ dep_sect = _MODULE_ORDER_ENTRY % constant.KEY_DEPENDENCIES
+ module_content = module_content.replace(_MODULE_DEP_TOKEN, dep_sect)
+ common_util.file_generate(module_iml_path, module_content)
+
+ # Only generate the dependencies.iml in the main module's folder.
+ dependencies_iml_path = None
+ if self.project_info.is_main_project:
+ dependencies_content = constant.FILE_IML.replace(_FACET_TOKEN, '')
+ dependencies_content = self._handle_source_folder(
+ dependencies_content, source_dict, False)
+ dependencies_content = self._handle_srcjar_folder(
+ dependencies_content,
+ self.project_info.source_path['srcjar_path'])
+ dependencies_content = self._handle_module_dependency(
+ dependencies_content, jar_dependencies)
+ dependencies_iml_path = os.path.join(
+ module_path, constant.KEY_DEPENDENCIES + _IML_EXTENSION)
+ common_util.file_generate(dependencies_iml_path,
+ dependencies_content)
+ logging.debug('Paired iml names are %s, %s', module_iml_path,
+ dependencies_iml_path)
+ # The dependencies_iml_path is use for removing the file itself in
+ # unittest.
+ return module_iml_path, dependencies_iml_path
+
+ def _generate_modules_xml(self, iml_path_list=None):
+ """Generate modules.xml file.
+
+ IntelliJ uses modules.xml to import which modules should be loaded to
+ project. In multiple modules case, we will pass iml_path_list of
+ submodules' dependencies and their iml file paths to add them into main
+ module's module.xml file. The dependencies.iml file contains all shared
+ dependencies source folders and jar files.
+
+ Args:
+ iml_path_list: A list of submodule iml paths.
+ """
+ module_path = self.project_info.project_absolute_path
+
+ # b/121256503: Prevent duplicated iml names from breaking IDEA.
+ module_name = self.get_unique_iml_name(module_path)
+
+ if iml_path_list is not None:
+ module_list = [
+ _MODULE_SECTION % (module_name, module_name),
+ _MODULE_SECTION % (constant.KEY_DEPENDENCIES,
+ constant.KEY_DEPENDENCIES)
+ ]
+ for iml_path in iml_path_list:
+ module_list.append(_SUB_MODULES_SECTION.format(IML=iml_path))
+ else:
+ module_list = [
+ _MODULE_SECTION % (module_name, module_name)
+ ]
+ module = '\n'.join(module_list)
+ content = self._remove_debugger_token(constant.MODULES_XML)
+ content = content.replace(_MODULE_TOKEN, module)
+ target_path = os.path.join(module_path, _IDEA_FOLDER, _MODULES_XML)
+ common_util.file_generate(target_path, content)
+
+ def _remove_debugger_token(self, content):
+ """Remove the token _ENABLE_DEBUGGER_MODULE_TOKEN.
+
+ Remove the token _ENABLE_DEBUGGER_MODULE_TOKEN in 2 cases:
+ 1. Sub projects don't need to be filled in the enable debugger module
+ so we remove the token here. For the main project, the enable
+ debugger module will be appended if it exists at the time launching
+ IDE.
+ 2. When there is no need to launch IDE.
+
+ Args:
+ content: The content of module.xml.
+
+ Returns:
+ String: The content of module.xml.
+ """
+ if (not self.project_info.config.is_launch_ide or
+ not self.project_info.is_main_project):
+ content = content.replace(_ENABLE_DEBUGGER_MODULE_TOKEN, '')
+ return content
+
+ def _get_project_git_path(self):
+ """Get the project's git path.
+
+ Return:
+ String: A module's git path.
+ """
+ module_path = self.project_info.project_absolute_path
+ # When importing whole Android repo, it shouldn't add vcs.xml,
+ # because IntelliJ doesn't handle repo as a version control.
+ if module_path == common_util.get_android_root_dir():
+ # TODO(b/135103553): Do a survey about: does devs want add
+ # every git into IntelliJ when importing whole Android.
+ return None
+ git_path = module_path
+ while not os.path.isdir(os.path.join(git_path, _GIT_FOLDER_NAME)):
+ git_path = str(pathlib.Path(git_path).parent)
+ if git_path == os.sep:
+ logging.warning('%s can\'t find its .git folder', module_path)
+ return None
+ return git_path
+
+
+def _get_exclude_content(root_path):
+ """Get the exclude folder content list.
+
+ It returns the exclude folders content list.
+ e.g.
+ ['<excludeFolder url="file://a/.idea" />',
+ '<excludeFolder url="file://a/.repo" />']
+
+ Args:
+ root_path: Android source file path.
+
+ Returns:
+ String: exclude folder content list.
+ """
+ exclude_items = []
+ for folder in _EXCLUDE_FOLDERS:
+ folder_path = os.path.join(root_path, folder)
+ if os.path.isdir(folder_path):
+ exclude_items.append(_EXCLUDE_ITEM % folder_path)
+ return exclude_items
def _trim_same_root_source(source_list):
@@ -456,175 +614,6 @@
return source == relative_path or source.startswith(relative_path + os.sep)
-# pylint: disable=too-many-locals
-def _generate_iml(root_path, module_path, source_dict, jar_dependencies,
- relative_path):
- """Generate iml file.
-
- Args:
- root_path: Android source tree root path.
- module_path: Absolute path of the module.
- source_dict: A dictionary of sources path with a flag to distinguish the
- path is test or source folder in IntelliJ.
- e.g.
- {'path_a': True, 'path_b': False}
- jar_dependencies: List of the jar path.
- relative_path: Relative path of the module.
-
- Returns:
- String: The absolute paths of module iml and dependencies iml.
- """
- template = _read_file_content(_TEMPLATE_IML_PATH)
-
- # Separate module and dependencies source folder
- project_source_dict = {}
- for source in list(source_dict):
- if _is_project_relative_source(source, relative_path):
- is_test = source_dict.get(source)
- source_dict.pop(source)
- project_source_dict.update({source: is_test})
-
- # Generate module iml.
- module_content = _handle_facet(template, module_path)
- module_content = _handle_source_folder(
- root_path, module_content, project_source_dict, True, relative_path)
- # b/121256503: Prevent duplicated iml names from breaking IDEA.
- module_name = get_unique_iml_name(module_path)
-
- module_iml_path = os.path.join(module_path, module_name + _IML_EXTENSION)
-
- dep_name = _get_dependencies_name(module_name)
- dep_sect = _MODULE_ORDER_ENTRY % dep_name
- module_content = module_content.replace(_MODULE_DEP_TOKEN, dep_sect)
- _file_generate(module_iml_path, module_content)
-
- # Generate dependencies iml.
- dependencies_content = template.replace(_FACET_TOKEN, '')
- dependencies_content = _handle_source_folder(
- root_path, dependencies_content, source_dict, False, relative_path)
- dependencies_content = _handle_module_dependency(
- root_path, dependencies_content, jar_dependencies)
- dependencies_iml_path = os.path.join(module_path, dep_name + _IML_EXTENSION)
- _file_generate(dependencies_iml_path, dependencies_content)
- logging.debug('Paired iml names are %s, %s', module_iml_path,
- dependencies_iml_path)
- # The dependencies_iml_path is use for removing the file itself in unittest.
- return module_iml_path, dependencies_iml_path
-
-
-def _generate_classpath(module_path, source_list, jar_dependencies):
- """Generate .classpath file.
-
- Args:
- module_path: Absolute path of the module.
- source_list: A list of sources path.
- jar_dependencies: List of the jar path.
-
- Returns:
- String: The absolute paths of .classpath.
- """
- template = _read_file_content(_ECLIP_TEMPLATE_PATH)
-
- src_list = [_ECLIP_SRC_ENTRY.format(s) for s in source_list]
- template = template.replace(_ECLIP_SRC_TOKEN, ''.join(src_list))
-
- lib_list = [_ECLIP_LIB_ENTRY.format(j) for j in jar_dependencies]
- template = template.replace(_ECLIP_LIB_TOKEN, ''.join(lib_list))
-
- classpath_path = os.path.join(module_path, _ECLIP_EXTENSION)
-
- _file_generate(classpath_path, template)
-
- return classpath_path
-
-
-def _generate_eclipse_project(project_name, module_path):
- """Generate .project file of Eclipse.
-
- Args:
- project_name: A string of the project name.
- module_path: Absolute path of the module.
- """
- template = _read_file_content(_ECLIP_PROJECT_PATH)
- template = template.replace(_ECLIP_PROJECT_NAME_TOKEN, project_name)
- eclipse_project = os.path.join(module_path, _ECLIP_PROJECT_EXTENSION)
- _file_generate(eclipse_project, template)
-
-
-def _get_dependencies_name(module_name):
- """Get module's dependencies iml name which will be written in module.xml.
-
- Args:
- module_name: The name will be appended to "dependencies-".
-
- Returns:
- String: The joined dependencies iml file name, e.g. "dependencies-core"
- """
- return '-'.join([_DEPENDENCIES, module_name])
-
-
-def _generate_modules_xml(module_path, iml_path_list=None):
- """Generate modules.xml file.
-
- IntelliJ uses modules.xml to import which modules should be loaded to
- project. Only in multiple modules case will we pass iml_path_list of
- submodules' dependencies and their iml file paths to add them into main
- module's module.xml file. The dependencies iml file names will be changed
- from original dependencies.iml to dependencies-[module_name].iml,
- e.g. dependencies-core.iml for core.iml.
-
- Args:
- module_path: Path of the module.
- iml_path_list: A list of submodule iml paths.
- """
- content = _read_file_content(_TEMPLATE_MODULES_PATH)
-
- # b/121256503: Prevent duplicated iml names from breaking IDEA.
- module_name = get_unique_iml_name(module_path)
-
- file_name = os.path.splitext(module_name)[0]
- dep_name = _get_dependencies_name(file_name)
- module_list = [
- _MODULE_SECTION % (module_name, module_name),
- _MODULE_SECTION % (dep_name, dep_name)
- ]
- if iml_path_list:
- for iml_path in iml_path_list:
- iml_dir, iml_name = os.path.split(iml_path)
- dep_file = _get_dependencies_name(iml_name)
- dep_path = os.path.join(iml_dir, dep_file)
- module_list.append(_SUB_MODULES_SECTION % (dep_path, dep_path))
- module_list.append(_SUB_MODULES_SECTION % (iml_path, iml_path))
- module = '\n'.join(module_list)
- content = content.replace(_MODULE_TOKEN, module)
- target_path = os.path.join(module_path, _IDEA_FOLDER, _MODULES_XML)
- _file_generate(target_path, content)
-
-
-def _generate_vcs_xml(module_path):
- """Generate vcs.xml file.
-
- IntelliJ use vcs.xml to record version control software's information.
- Since we are using a single project file, it will only contain the
- module itself. If there is no git folder inside, it would find it in
- parent's folder.
-
- Args:
- module_path: Path of the module.
-
- Return:
- String: A module's git path.
- """
- git_path = module_path
- while not os.path.isdir(os.path.join(git_path, _GIT_FOLDER_NAME)):
- git_path = str(pathlib.Path(git_path).parent)
- if git_path == os.sep:
- logging.warning('%s can\'t find its .git folder', module_path)
- return None
- _write_vcs_xml(module_path, [git_path])
- return git_path
-
-
def _write_vcs_xml(module_path, git_paths):
"""Write the git path into vcs.xml.
@@ -636,10 +625,9 @@
git_paths: A list of git path.
"""
_vcs_content = '\n'.join([_VCS_SECTION % p for p in git_paths if p])
- content = _read_file_content(_TEMPLATE_VCS_PATH)
- content = content.replace(_VCS_TOKEN, _vcs_content)
+ content = constant.VCS_XML.replace(_VCS_TOKEN, _vcs_content)
target_path = os.path.join(module_path, _IDEA_FOLDER, _VCS_XML)
- _file_generate(target_path, content)
+ common_util.file_generate(target_path, content)
def _merge_project_vcs_xmls(projects):
@@ -677,3 +665,69 @@
os.symlink(rel_source, rel_target)
except OSError as err:
logging.error('Not support to run aidegen on Windows.\n %s', err)
+
+
+def _filter_out_source_paths(source_paths, module_relpaths):
+ """Filter out the source paths which belong to the target module.
+
+ The source_paths is a union set of all source paths of all target modules.
+ For generating the dependencies.iml, we only need the source paths outside
+ the target modules.
+
+ Args:
+ source_paths: A set contains the source folder paths.
+ module_relpaths: A list, contains the relative paths of target modules
+ except the main module.
+
+ Returns: A set of source paths.
+ """
+ return {x for x in source_paths if not any(
+ {_is_source_under_relative_path(x, y) for y in module_relpaths})}
+
+
+def _merge_all_shared_source_paths(projects):
+ """Merge all source paths and jar paths into main project.
+
+ There should be no duplicate source root path in IntelliJ. The issue doesn't
+ happen in single project case. Once users choose multiple projects, there
+ could be several same source paths of different projects. In order to
+ prevent that, we should remove the source paths in dependencies.iml which
+ are duplicate with the paths in [module].iml files.
+
+ Args:
+ projects: A list of ProjectInfo instances.
+ """
+ main_project = projects[0]
+ # Merge all source paths of sub projects into main project.
+ for project in projects[1:]:
+ main_project.source_path['source_folder_path'].update(
+ project.source_path['source_folder_path'])
+ main_project.source_path['test_folder_path'].update(
+ project.source_path['test_folder_path'])
+ main_project.source_path['jar_path'].update(
+ project.source_path['jar_path'])
+ # Filter duplicate source/test paths from dependencies.iml.
+ sub_projects_relpaths = {p.project_relative_path for p in projects[1:]}
+ main_project.source_path['source_folder_path'] = _filter_out_source_paths(
+ main_project.source_path['source_folder_path'], sub_projects_relpaths)
+ main_project.source_path['test_folder_path'] = _filter_out_source_paths(
+ main_project.source_path['test_folder_path'], sub_projects_relpaths)
+
+
+def update_enable_debugger(module_path, enable_debugger_module_abspath=None):
+ """Append the enable_debugger module's info in modules.xml file.
+
+ Args:
+ module_path: A string of the folder path contains IDE project content,
+ e.g., the folder contains the .idea folder.
+ enable_debugger_module_abspath: A string of the im file path of enable
+ debugger module.
+ """
+ replace_string = ''
+ if enable_debugger_module_abspath:
+ replace_string = _SUB_MODULES_SECTION.format(
+ IML=enable_debugger_module_abspath)
+ target_path = os.path.join(module_path, _IDEA_FOLDER, _MODULES_XML)
+ content = common_util.read_file_content(target_path)
+ content = content.replace(_ENABLE_DEBUGGER_MODULE_TOKEN, replace_string)
+ common_util.file_generate(target_path, content)
diff --git a/aidegen/lib/project_file_gen_unittest.py b/aidegen/lib/project_file_gen_unittest.py
index c3a969a..ace6ec6 100644
--- a/aidegen/lib/project_file_gen_unittest.py
+++ b/aidegen/lib/project_file_gen_unittest.py
@@ -20,11 +20,11 @@
import os
import shutil
import unittest
-
from unittest import mock
from aidegen import constant
from aidegen import unittest_constants
+from aidegen.lib import common_util
from aidegen.lib import project_file_gen
from atest import module_info
@@ -35,15 +35,18 @@
maxDiff = None
_TEST_DATA_PATH = unittest_constants.TEST_DATA_PATH
- _ANDROID_PROJECT_PATH = os.path.join(_TEST_DATA_PATH, 'android_project')
+ _ANDROID_PROJECT_PATH = unittest_constants.ANDROID_PROJECT_PATH
_PROJECT_PATH = os.path.join(_TEST_DATA_PATH, 'project')
_ANDROID_FACET_SAMPLE = os.path.join(_TEST_DATA_PATH, 'android_facet.iml')
_PROJECT_FACET_SAMPLE = os.path.join(_TEST_DATA_PATH, 'project_facet.iml')
_MODULE_DEP_SAMPLE = os.path.join(_TEST_DATA_PATH, 'module_dependency.iml')
_IML_SAMPLE = os.path.join(_TEST_DATA_PATH, 'test.iml')
- _CLASSPATH_SAMPLE = os.path.join(_TEST_DATA_PATH, 'eclipse.classpath')
_DEPENDENCIES_IML_SAMPLE = os.path.join(_TEST_DATA_PATH, 'dependencies.iml')
_MODULE_XML_SAMPLE = os.path.join(_TEST_DATA_PATH, 'modules.xml')
+ _MAIN_MODULE_XML_SAMPLE = os.path.join(_TEST_DATA_PATH,
+ 'modules_only_self_module.xml')
+ _ENABLE_DEBUGGER_MODULE_SAMPLE = os.path.join(
+ _TEST_DATA_PATH, 'modules_with_enable_debugger.xml')
_VCS_XML_SAMPLE = os.path.join(_TEST_DATA_PATH, 'vcs.xml')
_IML_PATH = os.path.join(_ANDROID_PROJECT_PATH, 'android_project.iml')
_DEPENDENCIES_IML_PATH = os.path.join(_ANDROID_PROJECT_PATH,
@@ -52,49 +55,47 @@
_MODULE_PATH = os.path.join(_IDEA_PATH, 'modules.xml')
_VCS_PATH = os.path.join(_IDEA_PATH, 'vcs.xml')
_SOURCE_SAMPLE = os.path.join(_TEST_DATA_PATH, 'source.iml')
+ _SRCJAR_SAMPLE = os.path.join(_TEST_DATA_PATH, 'srcjar.iml')
_LOCAL_PATH_TOKEN = '@LOCAL_PATH@'
_AOSP_FOLDER = '/aosp'
- _JAR_DEP_LIST = ['test1.jar', 'test2.jar']
_TEST_SOURCE_LIST = [
'a/b/c/d', 'a/b/c/d/e', 'a/b/c/d/e/f', 'a/b/c/d/f', 'e/f/a', 'e/f/b/c',
'e/f/g/h'
]
- _ANDROID_SOURCE_DICT = {
- 'test_data/project/level11/level21': True,
- 'test_data/project/level11/level22/level31': False,
- 'test_data/project/level12/level22': False,
- }
_ANDROID_SOURCE_RELATIVE_PATH = 'test_data/project'
_SAMPLE_CONTENT_LIST = ['a/b/c/d', 'e/f']
_SAMPLE_TRIMMED_SOURCE_LIST = ['a/b/c/d', 'e/f/a', 'e/f/b/c', 'e/f/g/h']
+ _SAMPLE_EXCLUDE_FOLDERS = [
+ ' <excludeFolder url="file://%s/.idea" />\n'
+ % _TEST_DATA_PATH,
+ ' <excludeFolder url="file://%s/out" />\n' % _TEST_DATA_PATH,
+ ]
- def test_handle_facet_for_android(self):
+ @mock.patch('aidegen.lib.project_info.ProjectInfo')
+ def test_handle_facet_for_android(self, mock_project):
"""Test _handle_facet with android project."""
- template = project_file_gen._read_file_content(
- project_file_gen._TEMPLATE_IML_PATH)
- android_facet = project_file_gen._handle_facet(
- template, self._ANDROID_PROJECT_PATH)
- sample_android_facet = project_file_gen._read_file_content(
+ mock_project.project_absolute_path = self._ANDROID_PROJECT_PATH
+ android_facet = project_file_gen.ProjectFileGenerator(
+ mock_project)._handle_facet(constant.FILE_IML)
+ sample_android_facet = common_util.read_file_content(
self._ANDROID_FACET_SAMPLE)
self.assertEqual(android_facet, sample_android_facet)
- def test_handle_facet_for_normal(self):
+ @mock.patch('aidegen.lib.project_info.ProjectInfo')
+ def test_handle_facet_for_normal(self, mock_project):
"""Test _handle_facet with normal module."""
- template = project_file_gen._read_file_content(
- project_file_gen._TEMPLATE_IML_PATH)
- project_facet = project_file_gen._handle_facet(template,
- self._PROJECT_PATH)
- sample_project_facet = project_file_gen._read_file_content(
+ mock_project.project_absolute_path = self._PROJECT_PATH
+ project_facet = project_file_gen.ProjectFileGenerator(
+ mock_project)._handle_facet(constant.FILE_IML)
+ sample_project_facet = common_util.read_file_content(
self._PROJECT_FACET_SAMPLE)
self.assertEqual(project_facet, sample_project_facet)
def test_handle_module_dependency(self):
"""Test _module_dependency."""
- module_dependency = project_file_gen._read_file_content(
- project_file_gen._TEMPLATE_IML_PATH)
- module_dependency = module_dependency.replace(
+ module_dependency = constant.FILE_IML.replace(
project_file_gen._MODULE_DEP_TOKEN, '')
- correct_module_dep = project_file_gen._read_file_content(
+ correct_module_dep = common_util.read_file_content(
self._MODULE_DEP_SAMPLE)
self.assertEqual(correct_module_dep, module_dependency)
@@ -104,54 +105,97 @@
self._TEST_SOURCE_LIST[:])
self.assertEqual(url_list, self._SAMPLE_TRIMMED_SOURCE_LIST)
- def test_handle_source_folder(self):
+ @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+ @mock.patch('aidegen.lib.project_info.ProjectInfo')
+ def test_handle_source_folder(self, mock_project, mock_get_root):
"""Test _handle_source_folder."""
- template = project_file_gen._read_file_content(
- project_file_gen._TEMPLATE_IML_PATH)
- source = project_file_gen._handle_source_folder(
- self._AOSP_FOLDER, template,
- copy.deepcopy(self._ANDROID_SOURCE_DICT), True,
- self._ANDROID_SOURCE_RELATIVE_PATH)
- sample_source = project_file_gen._read_file_content(self._SOURCE_SAMPLE)
+ mock_get_root.return_value = self._AOSP_FOLDER
+ mock_project.project_relative_path = self._ANDROID_SOURCE_RELATIVE_PATH
+ source = project_file_gen.ProjectFileGenerator(
+ mock_project)._handle_source_folder(
+ constant.FILE_IML, copy.deepcopy(
+ unittest_constants.ANDROID_SOURCE_DICT), True)
+ sample_source = common_util.read_file_content(self._SOURCE_SAMPLE)
self.assertEqual(source, sample_source)
- def test_generate_iml(self):
+ @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+ @mock.patch('aidegen.lib.project_info.ProjectInfo')
+ def test_generate_iml(self, mock_project, mock_get_root):
"""Test _generate_iml."""
+ mock_get_root.return_value = self._AOSP_FOLDER
+ mock_project.project_absolute_path = self._ANDROID_PROJECT_PATH
+ mock_project.project_relative_path = self._ANDROID_SOURCE_RELATIVE_PATH
+ mock_project.source_path['jar_path'] = set(
+ unittest_constants.JAR_DEP_LIST)
+ pfile_gen = project_file_gen.ProjectFileGenerator(mock_project)
+ # Test for main project.
try:
- iml_path, dependencies_iml_path = project_file_gen._generate_iml(
- self._AOSP_FOLDER, self._ANDROID_PROJECT_PATH,
- copy.deepcopy(self._ANDROID_SOURCE_DICT), self._JAR_DEP_LIST,
- self._ANDROID_SOURCE_RELATIVE_PATH)
- test_iml = project_file_gen._read_file_content(iml_path)
- sample_iml = project_file_gen._read_file_content(self._IML_SAMPLE)
+ iml_path, dependencies_iml_path = pfile_gen._generate_iml(
+ copy.deepcopy(unittest_constants.ANDROID_SOURCE_DICT))
+ test_iml = common_util.read_file_content(iml_path)
+ sample_iml = common_util.read_file_content(self._IML_SAMPLE)
finally:
os.remove(iml_path)
- os.remove(dependencies_iml_path)
+ if dependencies_iml_path:
+ os.remove(dependencies_iml_path)
self.assertEqual(test_iml, sample_iml)
- def test_generate_modules_xml(self):
- """Test _generate_modules_xml."""
+ # Test for sub projects.
try:
- project_file_gen._generate_modules_xml(self._ANDROID_PROJECT_PATH)
- test_module = project_file_gen._read_file_content(self._MODULE_PATH)
+ iml_path, _ = pfile_gen._generate_iml(
+ copy.deepcopy(unittest_constants.ANDROID_SOURCE_DICT))
+ test_iml = common_util.read_file_content(iml_path)
+ sample_iml = common_util.read_file_content(self._IML_SAMPLE)
+ finally:
+ os.remove(iml_path)
+ self.assertEqual(test_iml, sample_iml)
+
+ @mock.patch('aidegen.lib.project_info.ProjectInfo')
+ def test_generate_modules_xml(self, mock_project):
+ """Test _generate_modules_xml."""
+ mock_project.project_absolute_path = self._ANDROID_PROJECT_PATH
+ pfile_gen = project_file_gen.ProjectFileGenerator(mock_project)
+ # Test for main project.
+ try:
+ pfile_gen._generate_modules_xml([])
+ project_file_gen.update_enable_debugger(self._ANDROID_PROJECT_PATH)
+ test_module = common_util.read_file_content(self._MODULE_PATH)
finally:
shutil.rmtree(self._IDEA_PATH)
- sample_module = project_file_gen._read_file_content(
- self._MODULE_XML_SAMPLE)
+ sample_module = common_util.read_file_content(self._MODULE_XML_SAMPLE)
self.assertEqual(test_module, sample_module)
- def test_generate_vcs_xml(self):
- """Test _generate_vcs_xml."""
+ # Test for sub projects which only has self module.
try:
- git_path = os.path.join(self._ANDROID_PROJECT_PATH,
- project_file_gen._GIT_FOLDER_NAME)
- os.mkdir(git_path)
- project_file_gen._generate_vcs_xml(self._ANDROID_PROJECT_PATH)
- test_vcs = project_file_gen._read_file_content(self._VCS_PATH)
+ pfile_gen._generate_modules_xml()
+ project_file_gen.update_enable_debugger(self._ANDROID_PROJECT_PATH)
+ test_module = common_util.read_file_content(self._MODULE_PATH)
finally:
shutil.rmtree(self._IDEA_PATH)
- shutil.rmtree(git_path)
- sample_vcs = project_file_gen._read_file_content(self._VCS_XML_SAMPLE)
+ sample_module = common_util.read_file_content(
+ self._MAIN_MODULE_XML_SAMPLE)
+ self.assertEqual(test_module, sample_module)
+
+ @mock.patch('os.path.isdir')
+ @mock.patch('aidegen.lib.project_info.ProjectInfo')
+ def test_get_project_git_path(self, mock_project, mock_isdir):
+ """Test _get_project_git_path."""
+ mock_project.project_absolute_path = '/a/b'
+ mock_isdir.return_value = True
+ expected_git_path = '/a/b'
+ pfile_gen = project_file_gen.ProjectFileGenerator(mock_project)
+ test_git_path = pfile_gen._get_project_git_path()
+ self.assertEqual(test_git_path, expected_git_path)
+
+ @mock.patch('aidegen.lib.project_info.ProjectInfo')
+ def test_merge_project_vcs_xmls(self, mock_project):
+ """Test _merge_project_vcs_xmls."""
+ mock_project.project_absolute_path = (
+ unittest_constants.ANDROID_PROJECT_PATH)
+ mock_project.git_path = unittest_constants.ANDROID_PROJECT_PATH
+ project_file_gen._merge_project_vcs_xmls([mock_project])
+ test_vcs = common_util.read_file_content(self._VCS_PATH)
+ sample_vcs = common_util.read_file_content(self._VCS_XML_SAMPLE)
# The sample must base on the real path.
sample_vcs = sample_vcs.replace(self._LOCAL_PATH_TOKEN,
self._ANDROID_PROJECT_PATH)
@@ -186,7 +230,8 @@
path_list.append(k)
print('{} {}.'.format('path list with length:', len(path_list)))
- names = [project_file_gen.get_unique_iml_name(f) for f in path_list]
+ names = [project_file_gen.ProjectFileGenerator.get_unique_iml_name(f)
+ for f in path_list]
print('{} {}.'.format('Names list with length:', len(names)))
self.assertEqual(len(names), len(path_list))
@@ -196,10 +241,12 @@
print('{} {}.'.format('The size of name set is:', len(dic)))
self.assertEqual(len(dic), len(path_list))
- def test_copy_project_files(self):
+ @mock.patch('aidegen.lib.project_info.ProjectInfo')
+ def test_copy_project_files(self, mock_project):
"""Test _copy_constant_project_files."""
- project_file_gen._copy_constant_project_files(
- self._ANDROID_PROJECT_PATH)
+ mock_project.project_absolute_path = self._ANDROID_PROJECT_PATH
+ project_file_gen.ProjectFileGenerator(
+ mock_project)._copy_constant_project_files()
self.assertTrue(
os.path.isfile(
os.path.join(self._IDEA_PATH,
@@ -217,28 +264,103 @@
'profiles_settings.xml')))
shutil.rmtree(self._IDEA_PATH)
- def test_generate_classpath(self):
- """Test _generate_classpath."""
- try:
- classpath = project_file_gen._generate_classpath(
- self._ANDROID_PROJECT_PATH,
- copy.deepcopy(list(sorted(self._ANDROID_SOURCE_DICT))),
- self._JAR_DEP_LIST)
- test_iml = project_file_gen._read_file_content(classpath)
- sample_iml = project_file_gen._read_file_content(
- self._CLASSPATH_SAMPLE)
- finally:
- os.remove(classpath)
- self.assertEqual(test_iml, sample_iml)
-
@mock.patch('os.symlink')
@mock.patch.object(os.path, 'exists')
def test_generate_git_ignore(self, mock_path_exist, mock_link):
"""Test _generate_git_ignore."""
mock_path_exist.return_value = True
- project_file_gen._generate_git_ignore(constant.AIDEGEN_ROOT_PATH)
+ project_file_gen._generate_git_ignore(
+ common_util.get_aidegen_root_dir())
self.assertFalse(mock_link.called)
+ def test_filter_out_source_paths(self):
+ """Test _filter_out_source_paths."""
+ test_set = {'a/a.java', 'b/b.java', 'c/c.java'}
+ module_relpath = {'a', 'c'}
+ expected_result = {'b/b.java'}
+ result_set = project_file_gen._filter_out_source_paths(test_set,
+ module_relpath)
+ self.assertEqual(result_set, expected_result)
+
+ @mock.patch('aidegen.lib.project_info.ProjectInfo')
+ @mock.patch('aidegen.lib.project_info.ProjectInfo')
+ def test_merge_all_source_paths(self, mock_main_project, mock_sub_project):
+ """Test _merge_all_shared_source_paths."""
+ mock_main_project.project_relative_path = 'main'
+ mock_main_project.source_path = {
+ 'source_folder_path': {
+ 'main/java.java',
+ 'sub/java.java',
+ 'share1/java.java'
+ },
+ 'test_folder_path': {'main/test.java', 'share1/test.java'},
+ 'jar_path': {'main/jar.jar', 'share1/jar.jar'},
+ 'r_java_path': {'out/R.java'},
+ }
+ mock_sub_project.project_relative_path = 'sub'
+ mock_sub_project.source_path = {
+ 'source_folder_path': {'sub/java.java', 'share2/java.java'},
+ 'test_folder_path': {'sub/test.java', 'share2/test.java'},
+ 'jar_path': {'sub/jar.jar', 'share2/jar.jar'},
+ 'r_java_path': {'out/R.java'},
+ }
+ expected_result = {
+ 'source_folder_path': {
+ 'main/java.java',
+ 'share1/java.java',
+ 'share2/java.java',
+ },
+ 'test_folder_path': {
+ 'main/test.java',
+ 'share1/test.java',
+ 'share2/test.java',
+ },
+ 'jar_path': {
+ 'main/jar.jar',
+ 'sub/jar.jar',
+ 'share1/jar.jar',
+ 'share2/jar.jar',
+ },
+ 'r_java_path': {'out/R.java'}
+ }
+ projects = [mock_main_project, mock_sub_project]
+ project_file_gen._merge_all_shared_source_paths(projects)
+ self.assertEqual(mock_main_project.source_path, expected_result)
+
+ def test_get_exclude_folders(self):
+ """Test _get_exclude_folders."""
+ exclude_folders = project_file_gen._get_exclude_content(
+ self._TEST_DATA_PATH)
+ self.assertEqual(self._SAMPLE_EXCLUDE_FOLDERS, exclude_folders)
+
+ @mock.patch('aidegen.lib.project_info.ProjectInfo')
+ def test_update_enable_debugger(self, mock_project):
+ """Test update_enable_debugger."""
+ enable_debugger_iml = '/path/to/enable_debugger/enable_debugger.iml'
+ sample_module = common_util.read_file_content(
+ self._ENABLE_DEBUGGER_MODULE_SAMPLE)
+ mock_project.project_absolute_path = self._ANDROID_PROJECT_PATH
+ pfile_gen = project_file_gen.ProjectFileGenerator(mock_project)
+ try:
+ pfile_gen._generate_modules_xml([])
+ project_file_gen.update_enable_debugger(self._ANDROID_PROJECT_PATH,
+ enable_debugger_iml)
+ test_module = common_util.read_file_content(self._MODULE_PATH)
+ self.assertEqual(test_module, sample_module)
+ finally:
+ shutil.rmtree(self._IDEA_PATH)
+
+ @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+ @mock.patch('aidegen.lib.project_info.ProjectInfo')
+ def test_handle_srcjar_folder(self, mock_project, mock_get_root):
+ """Test _handle_srcjar_folder."""
+ mock_get_root.return_value = self._AOSP_FOLDER
+ source = project_file_gen.ProjectFileGenerator(
+ mock_project)._handle_srcjar_folder(constant.FILE_IML,
+ {'out/aapt2.srcjar!/'})
+ sample_source = common_util.read_file_content(self._SRCJAR_SAMPLE)
+ self.assertEqual(source, sample_source)
+
if __name__ == '__main__':
unittest.main()
diff --git a/aidegen/lib/project_info.py b/aidegen/lib/project_info.py
index 55a600c..5c5dab7 100644
--- a/aidegen/lib/project_info.py
+++ b/aidegen/lib/project_info.py
@@ -23,10 +23,8 @@
from aidegen import constant
from aidegen.lib import common_util
-from aidegen.lib.common_util import COLORED_INFO
-from aidegen.lib.common_util import get_related_paths
+from aidegen.lib import module_info
-_KEY_ROBOTESTS = ['robotests', 'robolectric']
_ANDROID_MK = 'Android.mk'
_ANDROID_BP = 'Android.bp'
_CONVERT_MK_URL = ('https://android.googlesource.com/platform/build/soong/'
@@ -45,18 +43,20 @@
_EXCLUDE_MODULES = ['fake-framework']
-class ProjectInfo():
+class ProjectInfo:
"""Project information.
Class attributes:
- modules_info: A dict of all modules info by combining module-info.json
- with module_bp_java_deps.json.
+ modules_info: A AidegenModuleInfo instance whose name_to_module_info is
+ combining module-info.json with module_bp_java_deps.json.
+ config: A ProjectConfig instance which contains user preference of
+ project.
Attributes:
project_absolute_path: The absolute path of the project.
project_relative_path: The relative path of the project to
- constant.ANDROID_ROOT_PATH.
- project_module_names: A list of module names under project_absolute_path
+ common_util.get_android_root_dir().
+ project_module_names: A set of module names under project_absolute_path
directory or it's subdirectories.
dep_modules: A dict has recursively dependent modules of
project_module_names.
@@ -69,24 +69,37 @@
paths.
jar_path: A set contains the jar file paths.
jar_module_path: A dictionary contains the jar file and
- the module's path mapping.
+ the module's path mapping, only used in
+ Eclipse.
+ r_java_path: A set contains the relative path to the
+ R.java files, only used in Eclipse.
+ srcjar_path: A source content descriptor only used in
+ IntelliJ.
+ e.g. out/.../aapt2.srcjar!/
+ The "!/" is a content descriptor for
+ compressed files in IntelliJ.
+ is_main_project: A boolean to verify the project is main project.
"""
- modules_info = {}
+ modules_info = None
+ config = None
- def __init__(self, module_info, target=None):
+ def __init__(self, target=None, is_main_project=False):
"""ProjectInfo initialize.
Args:
- module_info: A ModuleInfo instance contains data of
- module-info.json.
target: Includes target module or project path from user input, when
locating the target, project with matching module name of
the given target has a higher priority than project path.
+ is_main_project: A boolean, default is False. True if the target is
+ the main project, otherwise False.
"""
- rel_path, abs_path = get_related_paths(module_info, target)
- target = self._get_target_name(target, abs_path)
- self.project_module_names = set(module_info.get_module_names(rel_path))
+ rel_path, abs_path = common_util.get_related_paths(self.modules_info,
+ target)
+ self.module_name = self._get_target_name(target, abs_path)
+ self.is_main_project = is_main_project
+ self.project_module_names = set(
+ self.modules_info.get_module_names(rel_path))
self.project_relative_path = rel_path
self.project_absolute_path = abs_path
self.iml_path = ''
@@ -94,7 +107,7 @@
self._init_source_path()
self.dep_modules = self.get_dep_modules()
self._filter_out_modules()
- self._display_convert_make_files_message(module_info, target)
+ self._display_convert_make_files_message()
def _set_default_modues(self):
"""Append default hard-code modules, source paths and jar files.
@@ -117,35 +130,25 @@
'source_folder_path': set(),
'test_folder_path': set(),
'jar_path': set(),
- 'jar_module_path': dict()
+ 'jar_module_path': dict(),
+ 'r_java_path': set(),
+ 'srcjar_path': set()
}
- def _display_convert_make_files_message(self, module_info, target):
- """Show message info users convert their Android.mk to Android.bp.
-
- Args:
- module_info: A ModuleInfo instance contains data of
- module-info.json.
- target: When locating the target module or project path from users'
- input, project with matching module name of the given target
- has a higher priority than project path.
- """
- mk_set = set(self._search_android_make_files(module_info))
+ def _display_convert_make_files_message(self):
+ """Show message info users convert their Android.mk to Android.bp."""
+ mk_set = set(self._search_android_make_files())
if mk_set:
print('\n{} {}\n'.format(
- COLORED_INFO('Warning:'),
- _ANDROID_MK_WARN.format(target, '\n'.join(mk_set))))
+ common_util.COLORED_INFO('Warning:'),
+ _ANDROID_MK_WARN.format(self.module_name, '\n'.join(mk_set))))
- def _search_android_make_files(self, module_info):
+ def _search_android_make_files(self):
"""Search project and dependency modules contain Android.mk files.
If there is only Android.mk but no Android.bp, we'll show the warning
message, otherwise we won't.
- Args:
- module_info: A ModuleInfo instance contains data of
- module-info.json.
-
Yields:
A string: the relative path of Android.mk.
"""
@@ -153,70 +156,59 @@
android_bp = os.path.join(self.project_absolute_path, _ANDROID_BP)
if os.path.isfile(android_mk) and not os.path.isfile(android_bp):
yield '\t' + os.path.join(self.project_relative_path, _ANDROID_MK)
- for module_name in self.dep_modules:
- rel_path, abs_path = get_related_paths(module_info, module_name)
- mod_mk = os.path.join(abs_path, _ANDROID_MK)
- mod_bp = os.path.join(abs_path, _ANDROID_BP)
- if os.path.isfile(mod_mk) and not os.path.isfile(mod_bp):
- yield '\t' + os.path.join(rel_path, _ANDROID_MK)
+ for mod_name in self.dep_modules:
+ rel_path, abs_path = common_util.get_related_paths(
+ self.modules_info, mod_name)
+ if rel_path and abs_path:
+ mod_mk = os.path.join(abs_path, _ANDROID_MK)
+ mod_bp = os.path.join(abs_path, _ANDROID_BP)
+ if os.path.isfile(mod_mk) and not os.path.isfile(mod_bp):
+ yield '\t' + os.path.join(rel_path, _ANDROID_MK)
- def set_modules_under_project_path(self):
- """Find modules whose class is qualified to be included under the
- project path.
+ def _get_modules_under_project_path(self, rel_path):
+ """Find modules under the rel_path.
+
+ Find modules whose class is qualified to be included as a target module.
+
+ Args:
+ rel_path: A string, the project's relative path.
+
+ Returns:
+ A set of module names.
"""
logging.info('Find modules whose class is in %s under %s.',
- common_util.TARGET_CLASSES, self.project_relative_path)
- for name, data in self.modules_info.items():
- if common_util.is_project_path_relative_module(
- data, self.project_relative_path):
- if self._is_a_target_module(data):
- self.project_module_names.add(name)
- if self._is_a_robolectric_module(data):
- self.project_module_names.add(_ROBOLECTRIC_MODULE)
+ constant.TARGET_CLASSES, rel_path)
+ modules = set()
+ for name, data in self.modules_info.name_to_module_info.items():
+ if common_util.is_project_path_relative_module(data, rel_path):
+ if module_info.AidegenModuleInfo.is_target_module(data):
+ modules.add(name)
else:
logging.debug(_NOT_TARGET, name, data['class'],
- common_util.TARGET_CLASSES)
+ constant.TARGET_CLASSES)
+ return modules
+
+ def _get_robolectric_dep_module(self, modules):
+ """Return the robolectric module set as dependency if any module is a
+ robolectric test.
+
+ Args:
+ modules: A set of modules.
+
+ Returns:
+ A set with a robolectric_all module name if one of the modules
+ needs the robolectric test module. Otherwise return empty list.
+ """
+ for module in modules:
+ if self.modules_info.is_robolectric_test(module):
+ return set([_ROBOLECTRIC_MODULE])
+ return set()
def _filter_out_modules(self):
"""Filter out unnecessary modules."""
for module in _EXCLUDE_MODULES:
self.dep_modules.pop(module, None)
- @staticmethod
- def _is_a_target_module(data):
- """Determine if the module is a target module.
-
- A module's class is in {'APPS', 'JAVA_LIBRARIES', 'ROBOLECTRIC'}
-
- Args:
- data: the module-info dictionary of the checked module.
-
- Returns:
- A boolean, true if is a target module, otherwise false.
- """
- if not 'class' in data:
- return False
- return any(x in data['class'] for x in common_util.TARGET_CLASSES)
-
- @staticmethod
- def _is_a_robolectric_module(data):
- """Determine if the module is a robolectric module.
-
- Hardcode for robotest dependency. If a folder named robotests or
- robolectric is in the module's path hierarchy then add the module
- Robolectric_all as a dependency.
-
- Args:
- data: the module-info dictionary of the checked module.
-
- Returns:
- A boolean, true if robolectric, otherwise false.
- """
- if not 'path' in data:
- return False
- path = data['path'][0]
- return any(key_dir in path.split(os.sep) for key_dir in _KEY_ROBOTESTS)
-
def get_dep_modules(self, module_names=None, depth=0):
"""Recursively find dependent modules of the project.
@@ -242,7 +234,7 @@
2. m3 is in the result as it has the same path to m1.
Args:
- module_names: A list of module names.
+ module_names: A set of module names.
depth: An integer shows the depth of module dependency referenced by
source. Zero means the max module depth.
@@ -252,28 +244,29 @@
dep = {}
children = set()
if not module_names:
- self.set_modules_under_project_path()
module_names = self.project_module_names
+ module_names.update(self._get_modules_under_project_path(
+ self.project_relative_path))
+ module_names.update(self._get_robolectric_dep_module(module_names))
self.project_module_names = set()
for name in module_names:
- if (name in self.modules_info
+ if (name in self.modules_info.name_to_module_info
and name not in self.project_module_names):
- dep[name] = self.modules_info[name]
+ dep[name] = self.modules_info.name_to_module_info[name]
dep[name][constant.KEY_DEPTH] = depth
self.project_module_names.add(name)
- if (constant.KEY_DEP in dep[name]
- and dep[name][constant.KEY_DEP]):
- children.update(dep[name][constant.KEY_DEP])
+ if (constant.KEY_DEPENDENCIES in dep[name]
+ and dep[name][constant.KEY_DEPENDENCIES]):
+ children.update(dep[name][constant.KEY_DEPENDENCIES])
if children:
dep.update(self.get_dep_modules(children, depth + 1))
return dep
@staticmethod
- def generate_projects(module_info, targets):
+ def generate_projects(targets):
"""Generate a list of projects in one time by a list of module names.
Args:
- module_info: An Atest module-info instance.
targets: A list of target modules or project paths from user input,
when locating the target, project with matched module name
of the target has a higher priority than project path.
@@ -281,7 +274,7 @@
Returns:
List: A list of ProjectInfo instances.
"""
- return [ProjectInfo(module_info, target) for target in targets]
+ return [ProjectInfo(target, i == 0) for i, target in enumerate(targets)]
@staticmethod
def _get_target_name(target, abs_path):
@@ -300,6 +293,6 @@
Returns:
A string, the target name.
"""
- if abs_path == constant.ANDROID_ROOT_PATH:
+ if abs_path == common_util.get_android_root_dir():
return os.path.basename(abs_path)
return target
diff --git a/aidegen/lib/project_info_unittest.py b/aidegen/lib/project_info_unittest.py
index e3f5bb7..2fa7ee0 100644
--- a/aidegen/lib/project_info_unittest.py
+++ b/aidegen/lib/project_info_unittest.py
@@ -20,11 +20,9 @@
import unittest
from unittest import mock
-from aidegen import constant
+from aidegen import unittest_constants
+from aidegen.lib import common_util
from aidegen.lib import project_info
-from aidegen.lib.project_info import ProjectInfo
-
-import aidegen.unittest_constants as uc
_MODULE_INFO = {
'm1': {'class': ['JAVA_LIBRARIES'], 'dependencies': ['m2', 'm6'],
@@ -60,62 +58,28 @@
@mock.patch('atest.module_info.ModuleInfo')
def test_get_dep_modules(self, mock_module_info):
"""Test get_dep_modules recursively find dependent modules."""
+ mock_module_info.name_to_module_info = _MODULE_INFO
mock_module_info.is_module.return_value = True
mock_module_info.get_paths.return_value = ['m1']
mock_module_info.get_module_names.return_value = ['m1']
- proj_info = project_info.ProjectInfo(mock_module_info,
- self.args.module_name)
- proj_info.modules_info = _MODULE_INFO
- proj_info.dep_modules = proj_info.get_dep_modules()
+ project_info.ProjectInfo.modules_info = mock_module_info
+ proj_info = project_info.ProjectInfo(self.args.module_name, False)
self.assertEqual(proj_info.dep_modules, _EXPECT_DEPENDENT_MODULES)
- def test_is_a_target_module(self):
- """Test _is_a_target_module with different conditions."""
- self.assertEqual(ProjectInfo._is_a_target_module({}), False)
- self.assertEqual(ProjectInfo._is_a_target_module({'path': ''}), False)
- self.assertEqual(ProjectInfo._is_a_target_module({'class': ''}), False)
- self.assertEqual(
- ProjectInfo._is_a_target_module({
- 'class': ['APPS']
- }), True)
- self.assertEqual(
- ProjectInfo._is_a_target_module({
- 'class': ['JAVA_LIBRARIES']
- }), True)
- self.assertEqual(
- ProjectInfo._is_a_target_module({
- 'class': ['ROBOLECTRIC']
- }), True)
-
- def test_is_a_robolectric_module(self):
- """Test _is_a_robolectric_module with different conditions."""
- self.assertEqual(ProjectInfo._is_a_robolectric_module({}), False)
- self.assertEqual(
- ProjectInfo._is_a_robolectric_module({
- 'path': [uc.TEST_PATH]
- }), False)
- self.assertEqual(
- ProjectInfo._is_a_robolectric_module({
- 'path': ['path/robotests']
- }), True)
- self.assertEqual(
- ProjectInfo._is_a_robolectric_module({
- 'path': ['path/robolectric']
- }), True)
- self.assertEqual(
- ProjectInfo._is_a_robolectric_module({
- 'path': ['robotests/robolectric']
- }), True)
-
- def test_get_target_name(self):
+ @mock.patch.object(common_util, 'get_android_root_dir')
+ def test_get_target_name(self, mock_get_root):
"""Test _get_target_name with different conditions."""
- constant.ANDROID_ROOT_PATH = uc.TEST_DATA_PATH
+ mock_get_root.return_value = unittest_constants.TEST_DATA_PATH
self.assertEqual(
- ProjectInfo._get_target_name(uc.TEST_MODULE, uc.TEST_DATA_PATH),
- os.path.basename(uc.TEST_DATA_PATH))
+ project_info.ProjectInfo._get_target_name(
+ unittest_constants.TEST_MODULE,
+ unittest_constants.TEST_DATA_PATH),
+ os.path.basename(unittest_constants.TEST_DATA_PATH))
self.assertEqual(
- ProjectInfo._get_target_name(uc.TEST_MODULE, uc.TEST_PATH),
- uc.TEST_MODULE)
+ project_info.ProjectInfo._get_target_name(
+ unittest_constants.TEST_MODULE,
+ unittest_constants.TEST_PATH),
+ unittest_constants.TEST_MODULE)
if __name__ == '__main__':
diff --git a/aidegen/lib/sdk_config.py b/aidegen/lib/sdk_config.py
new file mode 100644
index 0000000..2c73366
--- /dev/null
+++ b/aidegen/lib/sdk_config.py
@@ -0,0 +1,344 @@
+#!/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.
+
+"""Config JDK/SDK in jdk.table.xml.
+
+Depends on Linux and Mac there are different config file paths, such as
+jdk.table.xml, template_jdk_file.xml, etc. The information is saved in IdeUtil
+class and it uses SDKConfig class to write JDK/SDK configuration in
+jdk.table.xml.
+
+ Usage example:
+ 1.The parameters jdk_table_xml_file, jdk_template_path, default_jdk_path and
+ default_android_sdk_path are generated in IdeUtil class by different OS
+ and IDE. Please reference to ide_util.py for more detail information.
+ 2.Configure JDK and Android SDK in jdk.table.xml for IntelliJ and Android
+ Studio.
+ 3.Generate the enable_debugger module in IntelliJ.
+
+ sdk_config = SDKConfig(jdk_table_xml_file,
+ jdk_template_path,
+ default_jdk_path,
+ default_android_sdk_path)
+ sdk_config.config_jdk_file()
+ sdk_config.gen_enable_debugger_module()
+"""
+
+from __future__ import absolute_import
+
+import logging
+import os
+import re
+import xml.dom.minidom
+
+from aidegen.lib import common_util
+from aidegen.lib import config
+from aidegen.lib import errors
+from aidegen.lib import project_file_gen
+
+_API_LEVEL = 'api_level'
+_PLATFORMS = 'platforms'
+
+
+class SDKConfig():
+ """SDK config.
+
+ Instance attributes:
+ config_file: The absolute file path of the jdk.table.xml, the file
+ might not exist.
+ template_jdk_file: The JDK table template file path.
+ jdk_path: The path of JDK in android project.
+ config_exists: A boolean, True if the config_file exists otherwise
+ False.
+ config_string: A string, it will be written into config_file.
+ xml_dom: An object which contains the xml file parsing result of the
+ config_file.
+ jdks: An element list with tag named "jdk" in xml_dom.
+
+ Class attrubute:
+ android_sdk_path: The path to the Android SDK, None if the Android SDK
+ doesn't exist.
+ max_api_level: An integer, parsed from the folder named android-{n}
+ under Android/Sdk/platforms.
+ """
+
+ android_sdk_path = None
+ max_api_level = 0
+ _TARGET_JDK_NAME_TAG = '<name value="JDK18" />'
+ _COMPONENT_END_TAG = ' </component>'
+ _XML_CONTENT = ('<application>\n <component name="ProjectJdkTable">\n'
+ ' </component>\n</application>\n')
+ _TAG_JDK = 'jdk'
+ _TAG_NAME = 'name'
+ _TAG_TYPE = 'type'
+ _TAG_ADDITIONAL = 'additional'
+ _ATTRIBUTE_VALUE = 'value'
+ _ATTRIBUTE_JDK = _TAG_JDK
+ _ATTRIBUTE_SDK = 'sdk'
+ _TYPE_JAVASDK = 'JavaSDK'
+ _NAME_JDK18 = 'JDK18'
+ _TYPE_ANDROID_SDK = 'Android SDK'
+ _INPUT_QUERY_TIMES = 3
+ _API_FOLDER_RE = re.compile(r'platforms/android-(?P<api_level>[\d]+)')
+ _API_LEVEL_RE = re.compile(r'android-(?P<api_level>[\d]+)')
+ _ROOT_DIR = common_util.get_aidegen_root_dir()
+ _TEMPLATE_ANDROID_SDK = os.path.join(
+ _ROOT_DIR, 'templates/jdkTable/part.android.sdk.xml')
+ _ENTER_ANDROID_SDK_PATH = ('\nThe Android SDK folder:{} doesn\'t exist. '
+ 'The debug function "Attach debugger to Android '
+ 'process" is disabled without Android SDK in '
+ 'IntelliJ. Please set it up to enable the '
+ 'function. \nPlease enter the absolute path '
+ 'to Android SDK:')
+ _WARNING_API_LEVEL = ('Cannot find the Android SDK API folder from {}. '
+ 'Please install the SDK platform, otherwise the '
+ 'debug function "Attach debugger to Android process" '
+ 'cannot be enabled in IntelliJ.')
+ _WARNING_PARSE_XML_FAILED = ('The content of jdk.table.xml is not a valid'
+ 'xml.')
+
+ def __init__(self, config_file, template_jdk_file, jdk_path,
+ default_android_sdk_path):
+ """SDKConfig initialize.
+
+ Args:
+ config_file: The absolute file path of the jdk.table.xml, the file
+ might not exist.
+ template_jdk_file: The JDK table template file path.
+ jdk_path: The path of JDK in android project.
+ default_android_sdk_path: The default path to the Android SDK, it
+ might not exist.
+ """
+ self.config_file = config_file
+ self.template_jdk_file = template_jdk_file
+ self.jdk_path = jdk_path
+ self.config_exists = os.path.isfile(config_file)
+ self.config_string = self._get_default_config_content()
+ self._parse_xml()
+ SDKConfig.android_sdk_path = default_android_sdk_path
+
+ def _parse_xml(self):
+ """Parse the content of jdk.table.xml to a minidom object."""
+ try:
+ self.xml_dom = xml.dom.minidom.parseString(self.config_string)
+ self.jdks = self.xml_dom.getElementsByTagName(self._TAG_JDK)
+ except (TypeError, AttributeError) as err:
+ print('\n{} {}\n'.format(common_util.COLORED_INFO('Warning:'),
+ self._WARNING_PARSE_XML_FAILED))
+ raise errors.InvalidXMLError(err)
+
+ def _get_default_config_content(self):
+ """Get the default content of self.config_file.
+
+ If self.config_file exists, read the content as default. Otherwise, load
+ the content of self.template_jdk_file.
+
+ Returns:
+ String: The content will be written into self.config_file.
+ """
+ if self.config_exists:
+ with open(self.config_file) as config_file:
+ return config_file.read()
+ return self._XML_CONTENT
+
+ def _get_first_element_value(self, node, element_name):
+ """Get the first element if it exists in node.
+
+ Example:
+ The node:
+ <jdk version="2">
+ <name value="JDK18" />
+ <type value="JavaSDK" />
+ </jdk>
+ The element_name could be name or type. And the value of name is
+ JDK18, the value of type is JavaSDK.
+
+ Args:
+ node: A minidom object parsed from a xml.
+ element_name: A string of tag name in xml.
+
+ Returns:
+ String: None if the element_name doesn't exist.
+ """
+ elements = node.getElementsByTagName(element_name)
+ return (elements[0].getAttribute(self._ATTRIBUTE_VALUE) if elements
+ else None)
+
+ def _target_jdk_exists(self):
+ """Check if the JDK18 is already set in jdk.table.xml.
+
+ Returns:
+ Boolean: True if the JDK18 exists else False.
+ """
+ for jdk in self.jdks:
+ jdk_type = self._get_first_element_value(jdk, self._TAG_TYPE)
+ jdk_name = self._get_first_element_value(jdk, self._TAG_NAME)
+ if jdk_type == self._TYPE_JAVASDK and jdk_name == self._NAME_JDK18:
+ return True
+ return False
+
+ def _android_sdk_exists(self):
+ """Check if the Android SDK is already set in jdk.table.xml.
+
+ Returns:
+ Boolean: True if the Android SDK configuration exists, otherwise
+ False.
+ """
+ for jdk in self.jdks:
+ jdk_type = self._get_first_element_value(jdk, self._TAG_TYPE)
+ if jdk_type == self._TYPE_ANDROID_SDK:
+ return True
+ return False
+
+ @staticmethod
+ def _enter_android_sdk_path(input_message):
+ """Ask user input the path to Android SDK."""
+ return input(input_message)
+
+ @classmethod
+ def _get_android_sdk_path(cls):
+ """Get the Android SDK path.
+
+ Returns: The android sdk path if it exists, otherwise None.
+ """
+ # Set the maximum times require user to input the path of Android Sdk.
+ _check_times = cls._INPUT_QUERY_TIMES
+ while not cls._set_max_api_level():
+ if _check_times == 0:
+ cls.android_sdk_path = None
+ break
+ cls.android_sdk_path = cls._enter_android_sdk_path(
+ common_util.COLORED_FAIL(cls._ENTER_ANDROID_SDK_PATH.format(
+ cls.android_sdk_path)))
+ _check_times -= 1
+ return cls.android_sdk_path
+
+ @classmethod
+ def _set_max_api_level(cls):
+ """Set the max API level from Android SDK folder.
+
+ 1. Find the api folder such as android-28 in platforms folder.
+ 2. Parse the API level 28 from folder name android-28.
+
+ Returns: An integer, the max api level. Defatult is 0 if there is no
+ android-{x} folder under android_sdk_path.
+ """
+ platforms_dir = cls._get_platforms_dir_path()
+ if os.path.isdir(platforms_dir):
+ for abspath, _, _ in os.walk(platforms_dir):
+ match_api_folder = cls._API_FOLDER_RE.search(abspath)
+ if match_api_folder:
+ api_level = int(match_api_folder.group(_API_LEVEL))
+ if api_level > cls.max_api_level:
+ cls.max_api_level = api_level
+ return cls.max_api_level
+
+ @classmethod
+ def _get_platforms_dir_path(cls):
+ """Get the platform's dir path from user input Android SDK path.
+
+ Supposed users could input the SDK or platforms path:
+ e.g.
+ /.../Android/Sdk or /.../Android/Sdk/platforms
+ In order to minimize the search range for android-x folder, we check if
+ the last folder name is platforms from user input. If it is, return the
+ path, otherwise return the path which combines user input and platforms
+ folder name.
+
+ Returns: The platforms folder path string.
+ """
+ if cls.android_sdk_path.split(os.sep)[-1] == _PLATFORMS:
+ return cls.android_sdk_path
+ return os.path.join(cls.android_sdk_path, _PLATFORMS)
+
+ def _set_api_level_from_xml(self):
+ """Get the API level from jdk.table.xml."""
+ for jdk in self.jdks:
+ jdk_type = self._get_first_element_value(jdk, self._TAG_TYPE)
+ if jdk_type == self._TYPE_ANDROID_SDK:
+ additionals = jdk.getElementsByTagName(self._TAG_ADDITIONAL)
+ for additional in additionals:
+ jdk_value = additional.getAttribute(self._ATTRIBUTE_JDK)
+ sdk_value = additional.getAttribute(self._ATTRIBUTE_SDK)
+ if jdk_value == self._NAME_JDK18:
+ find_api_level = self._API_LEVEL_RE.match(sdk_value)
+ if find_api_level:
+ self.max_api_level = find_api_level.group(
+ 'api_level')
+ return True
+ logging.warning('Can\'t set api level from jdk.table.xml.')
+ return False
+
+ def _append_jdk_config_string(self, new_jdk_config):
+ """Add a jdk configuration at the last of <component>.
+
+ Args:
+ new_jdk_config: A string of new jdk configuration.
+ """
+ self.config_string = self.config_string.replace(
+ self._COMPONENT_END_TAG, new_jdk_config + self._COMPONENT_END_TAG)
+
+ def _write_jdk_config_file(self):
+ """Override the jdk.table.xml with the config_string."""
+ try:
+ common_util.file_generate(self.config_file, self.config_string)
+ except (IOError, OSError) as err:
+ logging.warning('Can\'t write the JDK info in %s.\n %s',
+ self.config_file, err)
+
+ def generate_jdk_config_string(self):
+ """Generate the default jdk configuration."""
+ if not self._target_jdk_exists():
+ self._append_jdk_config_string(
+ common_util.read_file_content(self.template_jdk_file))
+ self.config_string = self.config_string.format(JDKpath=self.jdk_path)
+
+ def generate_sdk_config_string(self):
+ """Generate Android SDK configuration."""
+ if not self._android_sdk_exists():
+ if self._get_android_sdk_path():
+ self._append_jdk_config_string(
+ common_util.read_file_content(self._TEMPLATE_ANDROID_SDK))
+ self.config_string = self.config_string.format(
+ ANDROID_SDK_PATH=SDKConfig.android_sdk_path,
+ API_LEVEL=SDKConfig.max_api_level)
+ else:
+ print('\n{} {}\n'.format(common_util.COLORED_INFO('Warning:'),
+ self._WARNING_API_LEVEL.format(
+ SDKConfig.max_api_level)))
+
+ def config_jdk_file(self):
+ """Generate the content of config and write it to the jdk.table.xml."""
+ if self._target_jdk_exists() and self._android_sdk_exists():
+ return
+ self.generate_jdk_config_string()
+ self.generate_sdk_config_string()
+ self._write_jdk_config_file()
+ # Parse the content of the updated jdk.table.xml to refresh self.jdks.
+ self._parse_xml()
+
+ def gen_enable_debugger_module(self, module_abspath):
+ """Generate the enable_debugger module under AIDEGen config folder.
+
+ Args:
+ module_abspath: the absolute path of the main project.
+ """
+ if self._set_api_level_from_xml():
+ with config.AidegenConfig() as aconf:
+ if aconf.create_enable_debugger_module(self.max_api_level):
+ project_file_gen.update_enable_debugger(
+ module_abspath,
+ config.AidegenConfig.DEBUG_ENABLED_FILE_PATH)
diff --git a/aidegen/lib/sdk_config_unittest.py b/aidegen/lib/sdk_config_unittest.py
new file mode 100644
index 0000000..80ae3ff
--- /dev/null
+++ b/aidegen/lib/sdk_config_unittest.py
@@ -0,0 +1,243 @@
+#!/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.
+
+"""Unittests for checking the jdk is generated in jdk.table.xml.."""
+
+import os
+import shutil
+import tempfile
+import unittest
+from unittest.mock import patch
+
+from aidegen import unittest_constants
+from aidegen.lib import common_util
+from aidegen.lib import sdk_config
+
+
+# pylint: disable=protected-access
+# pylint: disable=invalid-name
+class SDKConfigUnittests(unittest.TestCase):
+ """Unit tests for sdk_config.py"""
+ _JDK_FILE_NAME = 'jdk.table.xml'
+ _JDK_SAMPLE = os.path.join(unittest_constants.TEST_DATA_PATH,
+ 'jdk_table_xml', 'jdk18.xml')
+ _JDK_SAMPLE2 = os.path.join(unittest_constants.TEST_DATA_PATH,
+ 'jdk_table_xml', 'jdk_nonexistent.xml')
+ _JDK_SAMPLE3 = os.path.join(unittest_constants.TEST_DATA_PATH,
+ 'jdk_table_xml', 'android_sdk.xml')
+ _JDK_SAMPLE4 = os.path.join(unittest_constants.TEST_DATA_PATH,
+ 'jdk_table_xml', 'android_sdk_nonexistent.xml')
+ _JDK_TEMPLATE = os.path.join(common_util.get_aidegen_root_dir(),
+ 'templates', 'jdkTable', 'part.jdk.table.xml')
+ _JDK_PATH = os.path.join('/path', 'to', 'android', 'root',
+ 'prebuilts', 'jdk', 'jdk8', 'linux-x86')
+ _JDK_OTHER_CONTENT = """<application>
+ <component name="ProjectJdkTable">
+ <jdk version="2">
+ <name value="JDK_OTHER" />
+ <type value="JavaSDK" />
+ </jdk>
+ </component>
+</application>
+"""
+ _NONEXISTENT_ANDROID_SDK_PATH = os.path.join('/path', 'to', 'Android',
+ 'Sdk')
+ _DEFAULT_ANDROID_SDK_PATH = os.path.join(unittest_constants.TEST_DATA_PATH,
+ 'Android/Sdk')
+ _CUSTOM_ANDROID_SDK_PATH = os.path.join(unittest_constants.TEST_DATA_PATH,
+ 'Android/custom/sdk')
+
+ def test_generate_jdk_config(self):
+ """Test generating jdk config."""
+ expected_content = ''
+ tmp_folder = tempfile.mkdtemp()
+ config_file = os.path.join(tmp_folder, self._JDK_FILE_NAME)
+ try:
+ with open(self._JDK_SAMPLE) as sample:
+ expected_content = sample.read()
+ jdk = sdk_config.SDKConfig(config_file,
+ self._JDK_TEMPLATE,
+ self._JDK_PATH,
+ self._NONEXISTENT_ANDROID_SDK_PATH)
+ jdk.generate_jdk_config_string()
+ self.assertEqual(jdk.config_string, expected_content)
+ finally:
+ shutil.rmtree(tmp_folder)
+
+ def test_jdk_config_no_change(self):
+ """The config file exists and the JDK18 also exists.
+
+ In this case, there is nothing to do, make sure the config content is
+ not changed.
+ """
+ expected_content = ''
+ tmp_folder = tempfile.mkdtemp()
+ config_file = os.path.join(tmp_folder, self._JDK_FILE_NAME)
+ try:
+ with open(self._JDK_SAMPLE) as sample:
+ expected_content = sample.read()
+ # Reset the content of config file.
+ with open(config_file, 'w') as cf:
+ cf.write(expected_content)
+ jdk = sdk_config.SDKConfig(config_file,
+ self._JDK_TEMPLATE,
+ self._JDK_PATH,
+ self._DEFAULT_ANDROID_SDK_PATH)
+ jdk.generate_jdk_config_string()
+ self.assertEqual(jdk.config_string, expected_content)
+ finally:
+ shutil.rmtree(tmp_folder)
+
+ def test_append_jdk_config(self):
+ """The config file exists, test on the JDK18 does not exist."""
+ expected_content = ''
+ tmp_folder = tempfile.mkdtemp()
+ config_file = os.path.join(tmp_folder, self._JDK_FILE_NAME)
+ try:
+ with open(self._JDK_SAMPLE2) as sample:
+ expected_content = sample.read()
+ # Reset the content of config file.
+ with open(config_file, 'w') as cf:
+ cf.write(self._JDK_OTHER_CONTENT)
+ jdk = sdk_config.SDKConfig(config_file,
+ self._JDK_TEMPLATE,
+ self._JDK_PATH,
+ self._NONEXISTENT_ANDROID_SDK_PATH)
+ jdk.generate_jdk_config_string()
+ self.assertEqual(jdk.config_string, expected_content)
+ finally:
+ shutil.rmtree(tmp_folder)
+
+ def test_android_sdk_exist(self):
+ """Test to check the Android SDK configuration does exist."""
+ expected_content = ''
+ tmp_folder = tempfile.mkdtemp()
+ config_file = os.path.join(tmp_folder, self._JDK_FILE_NAME)
+ try:
+ with open(self._JDK_SAMPLE3) as sample:
+ expected_content = sample.read()
+ with open(config_file, 'w') as cf:
+ cf.write(expected_content)
+ jdk = sdk_config.SDKConfig(config_file,
+ self._JDK_TEMPLATE,
+ self._JDK_PATH,
+ self._NONEXISTENT_ANDROID_SDK_PATH)
+ self.assertEqual(jdk._android_sdk_exists(), True)
+ finally:
+ shutil.rmtree(tmp_folder)
+
+ def test_android_sdk_not_exist(self):
+ """Test to check the Android SDK configuration doesn't exist."""
+ expected_content = ''
+ tmp_folder = tempfile.mkdtemp()
+ config_file = os.path.join(tmp_folder, self._JDK_FILE_NAME)
+ try:
+ with open(self._JDK_SAMPLE4) as sample:
+ expected_content = sample.read()
+ with open(config_file, 'w') as cf:
+ cf.write(expected_content)
+ jdk = sdk_config.SDKConfig(config_file,
+ self._JDK_TEMPLATE,
+ self._JDK_PATH,
+ self._NONEXISTENT_ANDROID_SDK_PATH)
+ self.assertEqual(jdk._android_sdk_exists(), False)
+ finally:
+ shutil.rmtree(tmp_folder)
+
+ def test_get_android_sdk_path(self):
+ """Test get default Android SDK path."""
+ expected_content = self._DEFAULT_ANDROID_SDK_PATH
+ jdk = sdk_config.SDKConfig(self._JDK_SAMPLE4,
+ self._JDK_TEMPLATE,
+ self._JDK_PATH,
+ self._DEFAULT_ANDROID_SDK_PATH)
+ jdk._get_android_sdk_path()
+ self.assertEqual(jdk.android_sdk_path, expected_content)
+
+ def test_get_custom_android_sdk_path(self):
+ """Test get custom Android SDK path."""
+ api_folder = os.path.join(self._CUSTOM_ANDROID_SDK_PATH, 'platforms',
+ 'android-28')
+ os.makedirs(api_folder)
+ user_input = [self._CUSTOM_ANDROID_SDK_PATH]
+ sdk_config.SDKConfig.max_api_level = 0
+ expected_path = self._CUSTOM_ANDROID_SDK_PATH
+ try:
+ with patch('builtins.input', side_effect=user_input):
+ jdk = sdk_config.SDKConfig(self._JDK_SAMPLE4,
+ self._JDK_TEMPLATE,
+ self._JDK_PATH,
+ self._NONEXISTENT_ANDROID_SDK_PATH)
+ jdk._get_android_sdk_path()
+ self.assertEqual(jdk.android_sdk_path, expected_path)
+ finally:
+ shutil.rmtree(os.path.dirname(self._CUSTOM_ANDROID_SDK_PATH))
+
+ def test_set_max_api_level(self):
+ """Test set max API level."""
+ expected_api_level = 28
+ jdk = sdk_config.SDKConfig(self._JDK_SAMPLE3,
+ self._JDK_TEMPLATE,
+ self._JDK_PATH,
+ self._DEFAULT_ANDROID_SDK_PATH)
+ jdk._set_max_api_level()
+ self.assertEqual(jdk.max_api_level, expected_api_level)
+
+ # Test Android SDK platforms folder doesn't exist.
+ sdk_config.SDKConfig.max_api_level = 0
+ expected_api_level = 0
+ jdk = sdk_config.SDKConfig(self._JDK_SAMPLE3,
+ self._JDK_TEMPLATE,
+ self._JDK_PATH,
+ self._NONEXISTENT_ANDROID_SDK_PATH)
+ jdk._set_max_api_level()
+ self.assertEqual(jdk.max_api_level, expected_api_level)
+
+ def test_generate_android_sdk_config(self):
+ """Test generating Android SDK config."""
+ expected_content = ''
+ tmp_folder = tempfile.mkdtemp()
+ config_file = os.path.join(tmp_folder, self._JDK_FILE_NAME)
+ try:
+ with open(self._JDK_SAMPLE3) as sample:
+ expected_content = sample.read().replace(
+ self._NONEXISTENT_ANDROID_SDK_PATH,
+ self._DEFAULT_ANDROID_SDK_PATH)
+ jdk = sdk_config.SDKConfig(config_file,
+ self._JDK_TEMPLATE,
+ self._JDK_PATH,
+ self._NONEXISTENT_ANDROID_SDK_PATH)
+ user_input = [self._DEFAULT_ANDROID_SDK_PATH]
+ with patch('builtins.input', side_effect=user_input):
+ jdk.generate_sdk_config_string()
+ self.assertEqual(jdk.config_string, expected_content)
+ finally:
+ shutil.rmtree(tmp_folder)
+
+ def test_get_platforms_dir_path(self):
+ """Test _get_platforms_dir_path."""
+ expected_path = '/a/b/platforms'
+ sdk_config.SDKConfig.android_sdk_path = '/a/b'
+ test_path = sdk_config.SDKConfig._get_platforms_dir_path()
+ self.assertEqual(test_path, expected_path)
+
+ sdk_config.SDKConfig.android_sdk_path = '/a/b/platforms'
+ test_path = sdk_config.SDKConfig._get_platforms_dir_path()
+ self.assertEqual(test_path, expected_path)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/aidegen/lib/source_locator.py b/aidegen/lib/source_locator.py
index afa3bc2..fb4d39d 100644
--- a/aidegen/lib/source_locator.py
+++ b/aidegen/lib/source_locator.py
@@ -24,46 +24,46 @@
import re
from aidegen import constant
-from aidegen.lib import errors
from aidegen.lib import common_util
-from aidegen.lib.common_util import COLORED_INFO
+from aidegen.lib import errors
from atest import atest_utils
-from atest import constants
# Parse package name from the package declaration line of a java.
# Group matches "foo.bar" of line "package foo.bar;" or "package foo.bar"
_PACKAGE_RE = re.compile(r'\s*package\s+(?P<package>[^(;|\s)]+)\s*', re.I)
_ANDROID_SUPPORT_PATH_KEYWORD = 'prebuilts/sdk/current/'
-_JAR = '.jar'
-_TARGET_LIBS = [_JAR]
+# File extensions
+_JAR_EXT = '.jar'
+_JAVA_EXT = '.java'
+_KOTLIN_EXT = '.kt'
+_SRCJAR_EXT = '.srcjar'
+
+_TARGET_LIBS = [_JAR_EXT]
+_TARGET_FILES = [_JAVA_EXT, _KOTLIN_EXT]
_JARJAR_RULES_FILE = 'jarjar-rules.txt'
-_JAVA = '.java'
-_KOTLIN = '.kt'
-_TARGET_FILES = [_JAVA, _KOTLIN]
-_KEY_INSTALLED = 'installed'
_KEY_JARJAR_RULES = 'jarjar_rules'
_KEY_JARS = 'jars'
-_KEY_PATH = 'path'
-_KEY_SRCS = 'srcs'
_KEY_TESTS = 'tests'
-_SRCJAR = '.srcjar'
-_AAPT2_DIR = 'out/target/common/obj/APPS/%s_intermediates/aapt2'
-_AAPT2_SRCJAR = 'out/target/common/obj/APPS/%s_intermediates/aapt2.srcjar'
+_NAME_AAPT2 = 'aapt2'
+_TARGET_R_JAR = 'R.jar'
+_TARGET_AAPT2_SRCJAR = _NAME_AAPT2 + _SRCJAR_EXT
+_TARGET_BUILD_FILES = [_TARGET_AAPT2_SRCJAR, _TARGET_R_JAR]
_IGNORE_DIRS = [
# The java files under this directory have to be ignored because it will
# cause duplicated classes by libcore/ojluni/src/main/java.
'libcore/ojluni/src/lambda/java'
]
_DIS_ROBO_BUILD_ENV_VAR = {'DISABLE_ROBO_RUN_TESTS': 'true'}
-_SKIP_BUILD_WARN = (
- 'You choose "--skip-build". Skip building jar and module might increase '
- 'the risk of the absence of some jar or R/AIDL/logtags java files and '
- 'cause the red lines to appear in IDE tool.')
+# When we use atest_utils.build(), there is a command length limit on
+# soong_ui.bash. We reserve 5000 characters for rewriting the command line
+# in soong_ui.bash.
+_CMD_LENGTH_BUFFER = 5000
+# For each argument, it need a space to separate following argument.
+_BLANK_SIZE = 1
-def multi_projects_locate_source(projects, verbose, depth, ide_name,
- skip_build=True):
+def multi_projects_locate_source(projects, verbose):
"""Locate the paths of dependent source folders and jar files with projects.
Args:
@@ -71,16 +71,11 @@
as project relative path, project real path, project
dependencies.
verbose: A boolean, if true displays full build output.
- depth: An integer shows the depth of module dependency referenced by
- source. Zero means the max module depth.
- ide_name: A string stands for the IDE name, default is IntelliJ.
- skip_build: A boolean default to true, if true skip building jar and
- srcjar files, otherwise build them.
"""
- if skip_build:
- print('\n{} {}\n'.format(COLORED_INFO('Warning:'), _SKIP_BUILD_WARN))
for project in projects:
- locate_source(project, verbose, depth, ide_name, build=not skip_build)
+ locate_source(project, verbose, project.config.depth,
+ project.config.ide_name,
+ build=not project.config.is_skip_build)
def locate_source(project, verbose, depth, ide_name, build=True):
@@ -138,33 +133,83 @@
module.locate_sources_path()
dependencies['source_folder_path'].update(module.src_dirs)
dependencies['test_folder_path'].update(module.test_dirs)
+ dependencies['r_java_path'].update(module.r_java_paths)
+ dependencies['srcjar_path'].update(module.srcjar_paths)
_append_jars_as_dependencies(dependencies, module)
if module.build_targets:
rebuild_targets |= module.build_targets
if rebuild_targets:
if build:
- _build_dependencies(verbose, rebuild_targets)
+ _batch_build_dependencies(verbose, rebuild_targets)
locate_source(project, verbose, depth, ide_name, build=False)
else:
- logging.warning('Jar files or modules build failed:\n\t%s.',
+ logging.warning('Jar or srcjar files build failed:\n\t%s.',
'\n\t'.join(rebuild_targets))
-def _build_dependencies(verbose, rebuild_targets):
- """Build the jar or srcjar files of the modules if it don't exist.
+def _batch_build_dependencies(verbose, rebuild_targets):
+ """Batch build the jar or srcjar files of the modules if they don't exist.
+
+ Command line has the max length limit, MAX_ARG_STRLEN, and
+ MAX_ARG_STRLEN = (PAGE_SIZE * 32).
+ If the build command is longer than MAX_ARG_STRLEN, this function will
+ separate the rebuild_targets into chunks with size less or equal to
+ MAX_ARG_STRLEN to make sure it can be built successfully.
Args:
verbose: A boolean, if true displays full build output.
- rebuild_targets: A list of jar or srcjar files which do not exist.
+ rebuild_targets: A set of jar or srcjar files which do not exist.
"""
- logging.info(('Ready to build the jar or srcjar files.'))
- targets = ['-k']
- targets.extend(list(rebuild_targets))
- if not atest_utils.build(targets, verbose, _DIS_ROBO_BUILD_ENV_VAR):
+ logging.info('Ready to build the jar or srcjar files. Files count = %s',
+ str(len(rebuild_targets)))
+ arg_max = os.sysconf("SC_PAGE_SIZE") * 32 - _CMD_LENGTH_BUFFER
+ rebuild_targets = list(rebuild_targets)
+ for start, end in iter(_separate_build_targets(
+ rebuild_targets, arg_max)):
+ _build_target(rebuild_targets[start: end], verbose)
+
+
+def _build_target(targets, verbose):
+ """Build the jar or srcjar files.
+
+ Use -k to keep going when some targets can't be built or build failed.
+ Use -j to speed up building.
+
+ Args:
+ targets: A list of jar or srcjar files which need to build.
+ verbose: A boolean, if true displays full build output.
+ """
+ build_cmd = ['-k', '-j']
+ build_cmd.extend(list(targets))
+ if not atest_utils.build(build_cmd, verbose, _DIS_ROBO_BUILD_ENV_VAR):
message = ('Build failed!\n{}\nAIDEGen will proceed but dependency '
'correctness is not guaranteed if not all targets being '
'built successfully.'.format('\n'.join(targets)))
- print('\n{} {}\n'.format(COLORED_INFO('Warning:'), message))
+ print('\n{} {}\n'.format(common_util.COLORED_INFO('Warning:'),
+ message))
+
+
+def _separate_build_targets(build_targets, max_length):
+ """Separate the build_targets by limit the command size to max command
+ length.
+
+ Args:
+ build_targets: A list to be separated.
+ max_length: The max number of each build command length.
+
+ Yields:
+ The start index and end index of build_targets.
+ """
+ arg_len = 0
+ first_item_index = 0
+ for i, item in enumerate(build_targets):
+ arg_len = arg_len + len(item) + _BLANK_SIZE
+ if arg_len > max_length:
+ yield first_item_index, i
+ first_item_index = i
+ arg_len = len(item) + _BLANK_SIZE
+ if first_item_index < len(build_targets):
+ yield first_item_index, len(build_targets)
def _generate_moduledata(module_name, module_data, ide_name, project_relpath,
@@ -200,14 +245,14 @@
for jar in list(module.jar_files):
dependent_data['jar_module_path'].update({jar: module.module_path})
# Collecting the jar files of default core modules as dependencies.
- if constant.KEY_DEP in module.module_data:
+ if constant.KEY_DEPENDENCIES in module.module_data:
dependent_data['jar_path'].update([
- x for x in module.module_data[constant.KEY_DEP]
+ x for x in module.module_data[constant.KEY_DEPENDENCIES]
if common_util.is_target(x, _TARGET_LIBS)
])
-class ModuleData():
+class ModuleData:
"""ModuleData class.
Attributes:
@@ -218,6 +263,9 @@
src_dirs: A set to keep the unique source folder relative paths.
test_dirs: A set to keep the unique test folder relative paths.
jar_files: A set to keep the unique jar file relative paths.
+ r_java_paths: A set to keep the R folder paths to use in Eclipse.
+ srcjar_paths: A set to keep the srcjar source root paths to use in
+ IntelliJ.
referenced_by_jar: A boolean to check if the module is referenced by a
jar file.
build_targets: A set to keep the unique build target jar or srcjar file
@@ -263,6 +311,8 @@
self.src_dirs = set()
self.test_dirs = set()
self.jar_files = set()
+ self.r_java_paths = set()
+ self.srcjar_paths = set()
self.referenced_by_jar = False
self.build_targets = set()
self.missing_jars = set()
@@ -285,41 +335,75 @@
"""
return self.module_depth == 0
- def _is_module_in_apps(self):
- """Check if the current module is under packages/apps."""
- _apps_path = os.path.join('packages', 'apps')
- return self.module_path.startswith(_apps_path)
-
def _collect_r_srcs_paths(self):
"""Collect the source folder of R.java.
- For modules under packages/apps, check if exists an intermediates
- directory which contains R.java. If it does not exist, build the
- aapt2.srcjar of the module to generate. Build system will finally copy
- the R.java from a intermediates directory to the central R directory
- after building successfully. So set the central R directory
- out/target/common/R as a default source folder in IntelliJ.
+ Check if the path of aapt2.srcjar or R.jar exists, which is the value of
+ key "srcjars" in module_data. If the path of both 2 cases doesn't exist,
+ build it onto an intermediates directory.
+
+ For IntelliJ, we can set the srcjar file as a source root for
+ dependency. For Eclipse, we still use the R folder as dependencies until
+ we figure out how to set srcjar file as dependency.
+ # TODO(b/135594800): Set aapt2.srcjar or R.jar as a dependency in
+ Eclipse.
"""
if (self._is_app_module() and self._is_target_module() and
- self._is_module_in_apps()):
- # The directory contains R.java for apps in packages/apps.
- r_src_dir = _AAPT2_DIR % self.module_name
- if not os.path.exists(common_util.get_abs_path(r_src_dir)):
- self.build_targets.add(_AAPT2_SRCJAR % self.module_name)
- # In case the central R folder been deleted, uses the intermediate
- # folder as the dependency to R.java.
- self.src_dirs.add(r_src_dir)
- # Add the central R as a default source folder.
- self.src_dirs.add('out/target/common/R')
+ self._check_key(constant.KEY_SRCJARS)):
+ for srcjar in self.module_data[constant.KEY_SRCJARS]:
+ if not os.path.exists(common_util.get_abs_path(srcjar)):
+ self.build_targets.add(srcjar)
+ self._collect_srcjar_path(srcjar)
+ r_dir = self._get_r_dir(srcjar)
+ if r_dir:
+ self.r_java_paths.add(r_dir)
+
+ def _collect_srcjar_path(self, srcjar):
+ """Collect the source folders from a srcjar path.
+
+ Set the aapt2.srcjar or R.jar as source root:
+ Case aapt2.srcjar:
+ The source path string is
+ out/.../Bluetooth_intermediates/aapt2.srcjar
+ The source content descriptor is
+ out/.../Bluetooth_intermediates/aapt2.srcjar!/.
+ Case R.jar:
+ The source path string is out/soong/.../gen/R.jar.
+ The source content descriptor is out/soong/.../gen/R.jar!/.
+
+ Args:
+ srcjar: A file path string relative to ANDROID_BUILD_TOP, the build
+ target of the module to generate R.java.
+ """
+ if os.path.basename(srcjar) in _TARGET_BUILD_FILES:
+ self.srcjar_paths.add('%s!/' % srcjar)
+
+ @staticmethod
+ def _get_r_dir(srcjar):
+ """Get the source folder of R.java for Eclipse.
+
+ Args:
+ srcjar: A file path string, the build target of the module to
+ generate R.java.
+
+ Returns:
+ A relative source folder path string, and return None if the target
+ file name is not aapt2.srcjar or R.jar.
+ """
+ target_folder, target_file = os.path.split(srcjar)
+ if target_file == _TARGET_AAPT2_SRCJAR:
+ return os.path.join(target_folder, _NAME_AAPT2)
+ if target_file == _TARGET_R_JAR:
+ return os.path.join(target_folder, _NAME_AAPT2, 'R')
+ return None
def _init_module_path(self):
"""Inintialize self.module_path."""
- self.module_path = (self.module_data[_KEY_PATH][0]
- if _KEY_PATH in self.module_data
- and self.module_data[_KEY_PATH] else '')
+ self.module_path = (self.module_data[constant.KEY_PATH][0]
+ if self._check_key(constant.KEY_PATH) else '')
def _init_module_depth(self, depth):
- """Inintialize module depth's settings.
+ """Initialize module depth's settings.
Set the module's depth from module info when user have -d parameter.
Set the -d value from user input, default to 0.
@@ -342,16 +426,20 @@
def _check_jars_exist(self):
"""Check if jars exist."""
- return _KEY_JARS in self.module_data and self.module_data[_KEY_JARS]
+ return self._check_key(_KEY_JARS)
+
+ def _check_classes_jar_exist(self):
+ """Check if classes_jar exist."""
+ return self._check_key(constant.KEY_CLASSES_JAR)
def _collect_srcs_paths(self):
"""Collect source folder paths in src_dirs from module_data['srcs']."""
- if self._check_key(_KEY_SRCS):
+ if self._check_key(constant.KEY_SRCS):
scanned_dirs = set()
- for src_item in self.module_data[_KEY_SRCS]:
+ for src_item in self.module_data[constant.KEY_SRCS]:
src_dir = None
src_item = os.path.relpath(src_item)
- if src_item.endswith(_SRCJAR):
+ if src_item.endswith(_SRCJAR_EXT):
self._append_jar_from_installed(self.specific_soong_path)
elif common_util.is_target(src_item, _TARGET_FILES):
# Only scan one java file in each source directories.
@@ -380,10 +468,6 @@
src_dir: the directory to be added.
"""
if not any(path in src_dir for path in _IGNORE_DIRS):
- # Build the module if the source path not exists. The java is
- # normally generated for AIDL or logtags file.
- if not os.path.exists(common_util.get_abs_path(src_dir)):
- self.build_targets.add(self.module_name)
if self._is_test_module(src_dir):
self.test_dirs.add(src_dir)
else:
@@ -401,26 +485,12 @@
"""
return _KEY_TESTS in src_dir.split(os.sep)
- # pylint: disable=inconsistent-return-statements
- @staticmethod
- def _get_source_folder(java_file):
+ def _get_source_folder(self, java_file):
"""Parsing a java to get the package name to filter out source path.
- There are 3 steps to get the source path from a java.
- 1. Parsing a java to get package name.
- For example:
- The java_file is:path/to/the/module/src/main/java/com/android/
- first.java
- The package name of java_file is com.android.
- 2. Transfer package name to package path:
- For example:
- The package path of com.android is com/android.
- 3. Remove the package path and file name from the java path.
- For example:
- The path after removing package path and file name is
- path/to/the/module/src/main/java.
- As a result, path/to/the/module/src/main/java is the source path parsed
- from path/to/the/module/src/main/java/com/android/first.java.
+ Args:
+ java_file: A string, the java file with relative path.
+ e.g. path/to/the/java/file.java
Returns:
source_folder: A string of path to source folder(e.g. src/main/java)
@@ -428,14 +498,73 @@
"""
abs_java_path = common_util.get_abs_path(java_file)
if os.path.exists(abs_java_path):
- with open(abs_java_path) as data:
- for line in data.read().splitlines():
- match = _PACKAGE_RE.match(line)
- if match:
- package_name = match.group('package')
- package_path = package_name.replace(os.extsep, os.sep)
- source_folder, _, _ = java_file.rpartition(package_path)
- return source_folder.strip(os.sep)
+ package_name = self._get_package_name(abs_java_path)
+ if package_name:
+ return self._parse_source_path(java_file, package_name)
+ return None
+
+ @staticmethod
+ def _parse_source_path(java_file, package_name):
+ """Parse the source path by filter out the package name.
+
+ Case 1:
+ java file: a/b/c/d/e.java
+ package name: c.d
+ The source folder is a/b.
+
+ Case 2:
+ java file: a/b/c.d/e.java
+ package name: c.d
+ The source folder is a/b.
+
+ Case 3:
+ java file: a/b/c/d/e.java
+ package name: x.y
+ The source folder is a/b/c/d.
+
+ Case 4:
+ java file: a/b/c.d/e/c/d/f.java
+ package name: c.d
+ The source folder is a/b/c.d/e.
+
+ Case 5:
+ java file: a/b/c.d/e/c.d/e/f.java
+ package name: c.d.e
+ The source folder is a/b/c.d/e.
+
+ Args:
+ java_file: A string of the java file relative path.
+ package_name: A string of the java file's package name.
+
+ Returns:
+ A string, the source folder path.
+ """
+ java_file_name = os.path.basename(java_file)
+ pattern = r'%s/%s$' % (package_name, java_file_name)
+ search_result = re.search(pattern, java_file)
+ if search_result:
+ return java_file[:search_result.start()].strip(os.sep)
+ return os.path.dirname(java_file)
+
+ @staticmethod
+ def _get_package_name(abs_java_path):
+ """Get the package name by parsing a java file.
+
+ Args:
+ abs_java_path: A string of the java file with absolute path.
+ e.g. /root/path/to/the/java/file.java
+
+ Returns:
+ package_name: A string of package name.
+ """
+ package_name = None
+ with open(abs_java_path) as data:
+ for line in data.read().splitlines():
+ match = _PACKAGE_RE.match(line)
+ if match:
+ package_name = match.group('package')
+ break
+ return package_name
def _append_jar_file(self, jar_path):
"""Append a path to the jar file into self.jar_files if it's exists.
@@ -453,6 +582,13 @@
else:
self.missing_jars.add(jar_path)
return True
+ return False
+
+ def _append_classes_jar(self):
+ """Append the jar file as dependency for prebuilt modules."""
+ for jar in self.module_data[constant.KEY_CLASSES_JAR]:
+ if self._append_jar_file(jar):
+ break
def _append_jar_from_installed(self, specific_dir=None):
"""Append a jar file's path to the list of jar_files with matching
@@ -465,9 +601,8 @@
Args:
specific_dir: A string of path.
"""
- if (_KEY_INSTALLED in self.module_data
- and self.module_data[_KEY_INSTALLED]):
- for jar in self.module_data[_KEY_INSTALLED]:
+ if self._check_key(constant.KEY_INSTALLED):
+ for jar in self.module_data[constant.KEY_INSTALLED]:
if specific_dir and not jar.startswith(specific_dir):
continue
if self._append_jar_file(jar):
@@ -492,15 +627,15 @@
},
Path to the jar file is prebuilts/misc/common/asm/asm-6.0.jar.
"""
- if _KEY_JARS in self.module_data and self.module_data[_KEY_JARS]:
+ if self._check_key(_KEY_JARS):
for jar_name in self.module_data[_KEY_JARS]:
- if self._check_key(_KEY_INSTALLED):
+ if self._check_key(constant.KEY_INSTALLED):
self._append_jar_from_installed()
else:
jar_path = os.path.join(self.module_path, jar_name)
jar_abs = common_util.get_abs_path(jar_path)
- if not os.path.isfile(
- jar_abs) and jar_name.endswith('prebuilt.jar'):
+ if not os.path.isfile(jar_abs) and jar_name.endswith(
+ 'prebuilt.jar'):
rel_path = self._get_jar_path_from_prebuilts(jar_name)
if rel_path:
jar_path = rel_path
@@ -534,12 +669,12 @@
"""
rel_path = ''
search = os.sep.join(
- [constant.ANDROID_ROOT_PATH, 'prebuilts/**', jar_name])
+ [common_util.get_android_root_dir(), 'prebuilts/**', jar_name])
results = glob.glob(search, recursive=True)
if results:
jar_abs = results[0]
rel_path = os.path.relpath(
- jar_abs, os.environ.get(constants.ANDROID_BUILD_TOP, os.sep))
+ jar_abs, common_util.get_android_root_dir())
return rel_path
def locate_sources_path(self):
@@ -557,7 +692,14 @@
# If there is no source/tests folder of the module, reference the
# module by jar.
if not self.src_dirs and not self.test_dirs:
- self._append_jar_from_installed()
+ # Add the classes.jar from the classes_jar attribute as
+ # dependency if it exists. If the classes.jar doesn't exist,
+ # find the jar file from the installed attribute and add the jar
+ # as dependency.
+ if self._check_classes_jar_exist():
+ self._append_classes_jar()
+ else:
+ self._append_jar_from_installed()
self._collect_r_srcs_paths()
if self.referenced_by_jar and self.missing_jars:
self.build_targets |= self.missing_jars
@@ -622,5 +764,7 @@
self._append_jar_from_installed(self.specific_soong_path)
elif self._check_jars_exist():
self._set_jars_jarfile()
+ elif self._check_classes_jar_exist():
+ self._append_classes_jar()
else:
self._append_jar_from_installed()
diff --git a/aidegen/lib/source_locator_unittest.py b/aidegen/lib/source_locator_unittest.py
index af82732..1803677 100644
--- a/aidegen/lib/source_locator_unittest.py
+++ b/aidegen/lib/source_locator_unittest.py
@@ -23,7 +23,7 @@
from unittest import mock
from aidegen import constant
-from aidegen import unittest_constants as uc
+from aidegen import unittest_constants
from aidegen.lib import source_locator
_MODULE_NAME = 'test'
@@ -46,23 +46,42 @@
class SourceLocatorUnittests(unittest.TestCase):
"""Unit tests for source_locator.py"""
- def test_collect_srcs_paths(self):
+ @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+ def test_collect_srcs_paths(self, mock_android_root_dir):
"""Test _collect_srcs_paths create the source path list."""
result_source = set(['packages/apps/test/src/main/java'])
result_test = set(['packages/apps/test/tests'])
- constant.ANDROID_ROOT_PATH = uc.TEST_DATA_PATH
+ mock_android_root_dir.return_value = unittest_constants.TEST_DATA_PATH
module_data = source_locator.ModuleData(_MODULE_NAME, _MODULE_INFO,
_MODULE_DEPTH)
module_data._collect_srcs_paths()
self.assertEqual(module_data.src_dirs, result_source)
self.assertEqual(module_data.test_dirs, result_test)
- def test_get_source_folder(self):
+ def test_get_package_name(self):
+ """test get the package name from a java file."""
+ result_package_name = 'com.android'
+ test_java = os.path.join(unittest_constants.TEST_DATA_PATH,
+ _MODULE_PATH,
+ 'src/main/java/com/android/java.java')
+ package_name = source_locator.ModuleData._get_package_name(test_java)
+ self.assertEqual(package_name, result_package_name)
+
+ # Test on java file with no package name.
+ result_package_name = None
+ test_java = os.path.join(unittest_constants.TEST_DATA_PATH,
+ _MODULE_PATH,
+ 'src/main/java/com/android/no_package.java')
+ package_name = source_locator.ModuleData._get_package_name(test_java)
+ self.assertEqual(package_name, result_package_name)
+
+ @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+ def test_get_source_folder(self, mock_android_root_dir):
"""Test _get_source_folder process."""
# Test for getting the source path by parse package name from a java.
test_java = 'packages/apps/test/src/main/java/com/android/java.java'
result_source = 'packages/apps/test/src/main/java'
- constant.ANDROID_ROOT_PATH = uc.TEST_DATA_PATH
+ mock_android_root_dir.return_value = unittest_constants.TEST_DATA_PATH
module_data = source_locator.ModuleData(_MODULE_NAME, _MODULE_INFO,
_MODULE_DEPTH)
src_path = module_data._get_source_folder(test_java)
@@ -75,16 +94,122 @@
# Return path is None on the java file without package name.
test_java = ('packages/apps/test/src/main/java/com/android/'
- 'wrong_package.java')
+ 'no_package.java')
src_path = module_data._get_source_folder(test_java)
self.assertEqual(src_path, None)
- def test_append_jar_file(self):
+ def test_get_r_dir(self):
+ """Test get_r_dir."""
+ module_data = source_locator.ModuleData(_MODULE_NAME, _MODULE_INFO,
+ _MODULE_DEPTH)
+ # Test for aapt2.srcjar
+ test_aapt2_srcjar = 'a/aapt2.srcjar'
+ expect_result = 'a/aapt2'
+ r_dir = module_data._get_r_dir(test_aapt2_srcjar)
+ self.assertEqual(r_dir, expect_result)
+
+ # Test for R.jar
+ test_r_jar = 'b/R.jar'
+ expect_result = 'b/aapt2/R'
+ r_dir = module_data._get_r_dir(test_r_jar)
+ self.assertEqual(r_dir, expect_result)
+
+ # Test for the target file is not aapt2.srcjar or R.jar
+ test_unknown_target = 'c/proto.srcjar'
+ expect_result = None
+ r_dir = module_data._get_r_dir(test_unknown_target)
+ self.assertEqual(r_dir, expect_result)
+
+ @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+ def test_collect_r_src_path(self, mock_android_root_dir):
+ """Test collect_r_src_path."""
+ # Test on target srcjar exists in srcjars.
+ test_module = dict(_MODULE_INFO)
+ test_module['srcs'] = []
+ mock_android_root_dir.return_value = unittest_constants.TEST_DATA_PATH
+ module_data = source_locator.ModuleData(_MODULE_NAME, test_module,
+ _MODULE_DEPTH)
+ # Test the module is not APPS.
+ module_data._collect_r_srcs_paths()
+ expect_result = set()
+ self.assertEqual(module_data.r_java_paths, expect_result)
+
+ # Test the module is not a target module.
+ test_module['depth'] = 1
+ module_data = source_locator.ModuleData(_MODULE_NAME, test_module, 1)
+ module_data._collect_r_srcs_paths()
+ expect_result = set()
+ self.assertEqual(module_data.r_java_paths, expect_result)
+
+ # Test the srcjar target doesn't exist.
+ test_module['class'] = ['APPS']
+ test_module['srcjars'] = []
+ module_data = source_locator.ModuleData(_MODULE_NAME, test_module,
+ _MODULE_DEPTH)
+ module_data._collect_r_srcs_paths()
+ expect_result = set()
+ self.assertEqual(module_data.r_java_paths, expect_result)
+
+ # Test the srcjar target exists.
+ test_module['srcjars'] = [('out/soong/.intermediates/packages/apps/'
+ 'test_aapt2/aapt2.srcjar')]
+ module_data = source_locator.ModuleData(_MODULE_NAME, test_module,
+ _MODULE_DEPTH)
+ module_data._collect_r_srcs_paths()
+ expect_result = {
+ 'out/soong/.intermediates/packages/apps/test_aapt2/aapt2'
+ }
+ self.assertEqual(module_data.r_java_paths, expect_result)
+
+ def test_parse_source_path(self):
+ """Test _parse_source_path."""
+ # The package name of e.java is c.d.
+ test_java = 'a/b/c/d/e.java'
+ package_name = 'c.d'
+ expect_result = 'a/b'
+ src_path = source_locator.ModuleData._parse_source_path(test_java,
+ package_name)
+ self.assertEqual(src_path, expect_result)
+
+ # The package name of e.java is c.d.
+ test_java = 'a/b/c.d/e.java'
+ package_name = 'c.d'
+ expect_result = 'a/b'
+ src_path = source_locator.ModuleData._parse_source_path(test_java,
+ package_name)
+ self.assertEqual(src_path, expect_result)
+
+ # The package name of e.java is x.y.
+ test_java = 'a/b/c/d/e.java'
+ package_name = 'x.y'
+ expect_result = 'a/b/c/d'
+ src_path = source_locator.ModuleData._parse_source_path(test_java,
+ package_name)
+ self.assertEqual(src_path, expect_result)
+
+ # The package name of f.java is c.d.
+ test_java = 'a/b/c.d/e/c/d/f.java'
+ package_name = 'c.d'
+ expect_result = 'a/b/c.d/e'
+ src_path = source_locator.ModuleData._parse_source_path(test_java,
+ package_name)
+ self.assertEqual(src_path, expect_result)
+
+ # The package name of f.java is c.d.e.
+ test_java = 'a/b/c.d/e/c.d/e/f.java'
+ package_name = 'c.d.e'
+ expect_result = 'a/b/c.d/e'
+ src_path = source_locator.ModuleData._parse_source_path(test_java,
+ package_name)
+ self.assertEqual(src_path, expect_result)
+
+ @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+ def test_append_jar_file(self, mock_android_root_dir):
"""Test _append_jar_file process."""
# Append an existing jar file path to module_data.jar_files.
test_jar_file = os.path.join(_MODULE_PATH, 'test.jar')
result_jar_list = set([test_jar_file])
- constant.ANDROID_ROOT_PATH = uc.TEST_DATA_PATH
+ mock_android_root_dir.return_value = unittest_constants.TEST_DATA_PATH
module_data = source_locator.ModuleData(_MODULE_NAME, _MODULE_INFO,
_MODULE_DEPTH)
module_data._append_jar_file(test_jar_file)
@@ -102,7 +227,8 @@
module_data._append_jar_file(test_jar_file)
self.assertEqual(module_data.jar_files, set())
- def test_append_jar_from_installed(self):
+ @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+ def test_append_jar_from_installed(self, mock_android_root_dir):
"""Test _append_jar_from_installed handling."""
# Test appends the first jar file of 'installed'.
module_info = dict(_MODULE_INFO)
@@ -112,7 +238,7 @@
os.path.join(_MODULE_PATH, 'tests/test_second.jar')
]
result_jar_list = set([os.path.join(_MODULE_PATH, 'test.jar')])
- constant.ANDROID_ROOT_PATH = uc.TEST_DATA_PATH
+ mock_android_root_dir.return_value = unittest_constants.TEST_DATA_PATH
module_data = source_locator.ModuleData(_MODULE_NAME, module_info,
_MODULE_DEPTH)
module_data._append_jar_from_installed()
@@ -126,7 +252,8 @@
os.path.join(_MODULE_PATH, 'tests/'))
self.assertEqual(module_data.jar_files, result_jar_list)
- def test_set_jars_jarfile(self):
+ @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+ def test_set_jars_jarfile(self, mock_android_root_dir):
"""Test _set_jars_jarfile handling."""
# Combine the module path with jar file name in 'jars' and then append
# it to module_data.jar_files.
@@ -140,27 +267,29 @@
os.path.join(_MODULE_PATH, 'test.jar'),
os.path.join(_MODULE_PATH, 'tests/test_second.jar')
])
- constant.ANDROID_ROOT_PATH = uc.TEST_DATA_PATH
+ mock_android_root_dir.return_value = unittest_constants.TEST_DATA_PATH
module_data = source_locator.ModuleData(_MODULE_NAME, module_info,
_MODULE_DEPTH)
module_data._set_jars_jarfile()
self.assertEqual(module_data.jar_files, result_jar_list)
- def test_locate_sources_path(self):
+ @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+ def test_locate_sources_path(self, mock_android_root_dir):
"""Test locate_sources_path handling."""
# Test collect source path.
module_info = dict(_MODULE_INFO)
- result_src_list = set(['packages/apps/test/src/main/java',
- 'out/target/common/R'])
+ result_src_list = set(['packages/apps/test/src/main/java'])
result_test_list = set(['packages/apps/test/tests'])
result_jar_list = set()
- constant.ANDROID_ROOT_PATH = uc.TEST_DATA_PATH
+ result_r_path = set()
+ mock_android_root_dir.return_value = unittest_constants.TEST_DATA_PATH
module_data = source_locator.ModuleData(_MODULE_NAME, module_info,
_MODULE_DEPTH)
module_data.locate_sources_path()
self.assertEqual(module_data.src_dirs, result_src_list)
self.assertEqual(module_data.test_dirs, result_test_list)
self.assertEqual(module_data.jar_files, result_jar_list)
+ self.assertEqual(module_data.r_java_paths, result_r_path)
# Test find jar files.
jar_file = ('out/soong/.intermediates/packages/apps/test/test/'
@@ -192,7 +321,8 @@
module_data.locate_sources_path()
self.assertEqual(module_data.jar_files, result_jar_list)
- def test_collect_jar_by_depth_value(self):
+ @mock.patch('aidegen.lib.common_util.get_android_root_dir')
+ def test_collect_jar_by_depth_value(self, mock_android_root_dir):
"""Test parameter --depth handling."""
# Test find jar by module's depth greater than the --depth value from
# command line.
@@ -207,7 +337,7 @@
result_jar_list = set(
[('out/soong/.intermediates/packages/apps/test/test/'
'android_common/test.jar')])
- constant.ANDROID_ROOT_PATH = uc.TEST_DATA_PATH
+ mock_android_root_dir.return_value = unittest_constants.TEST_DATA_PATH
module_data = source_locator.ModuleData(_MODULE_NAME, module_info,
depth_by_source)
module_data.locate_sources_path()
@@ -219,41 +349,45 @@
depth_by_source = 2
module_info = dict(_MODULE_INFO)
module_info['depth'] = 2
- result_src_list = set(['packages/apps/test/src/main/java',
- 'out/target/common/R'])
+ result_src_list = set(['packages/apps/test/src/main/java'])
result_test_list = set(['packages/apps/test/tests'])
result_jar_list = set()
+ result_r_path = set()
module_data = source_locator.ModuleData(_MODULE_NAME, module_info,
depth_by_source)
module_data.locate_sources_path()
self.assertEqual(module_data.src_dirs, result_src_list)
self.assertEqual(module_data.test_dirs, result_test_list)
self.assertEqual(module_data.jar_files, result_jar_list)
+ self.assertEqual(module_data.r_java_paths, result_r_path)
# Test find source folder when module's depth smaller than the --depth
# value from command line.
depth_by_source = 3
module_info = dict(_MODULE_INFO)
module_info['depth'] = 2
- result_src_list = set(['packages/apps/test/src/main/java',
- 'out/target/common/R'])
+ result_src_list = set(['packages/apps/test/src/main/java'])
result_test_list = set(['packages/apps/test/tests'])
result_jar_list = set()
+ result_r_path = set()
module_data = source_locator.ModuleData(_MODULE_NAME, module_info,
depth_by_source)
module_data.locate_sources_path()
self.assertEqual(module_data.src_dirs, result_src_list)
self.assertEqual(module_data.test_dirs, result_test_list)
self.assertEqual(module_data.jar_files, result_jar_list)
+ self.assertEqual(module_data.r_java_paths, result_r_path)
+ @mock.patch('aidegen.lib.common_util.get_android_root_dir')
@mock.patch('aidegen.lib.project_info.ProjectInfo')
@mock.patch('atest.atest_utils.build')
- def test_locate_source(self, mock_atest_utils_build, mock_project_info):
+ def test_locate_source(self, mock_atest_utils_build, mock_project_info,
+ mock_android_root_dir):
"""Test locate_source handling."""
mock_atest_utils_build.build.return_value = True
test_root_path = os.path.join(tempfile.mkdtemp(), 'test')
- shutil.copytree(uc.TEST_DATA_PATH, test_root_path)
- constant.ANDROID_ROOT_PATH = test_root_path
+ shutil.copytree(unittest_constants.TEST_DATA_PATH, test_root_path)
+ mock_android_root_dir.return_value = test_root_path
generated_jar = ('out/soong/.intermediates/packages/apps/test/test/'
'android_common/generated.jar')
module_info = dict(_MODULE_INFO)
@@ -267,6 +401,8 @@
'test_folder_path': set(),
'jar_path': set(),
'jar_module_path': dict(),
+ 'srcjar_path': set(),
+ 'r_java_path': set(),
}
# Show warning when the jar not exists after build the module.
result_jar = set()
@@ -290,13 +426,15 @@
shutil.rmtree(test_root_path)
# Test collects source and test folders.
- result_source = set(['packages/apps/test/src/main/java',
- 'out/target/common/R'])
+ result_source = set(['packages/apps/test/src/main/java'])
result_test = set(['packages/apps/test/tests'])
+ result_r_path = set()
self.assertEqual(mock_project_info.source_path['source_folder_path'],
result_source)
self.assertEqual(mock_project_info.source_path['test_folder_path'],
result_test)
+ self.assertEqual(mock_project_info.source_path['r_java_path'],
+ result_r_path)
# Test loading jar from dependencies parameter.
default_jar = os.path.join(_MODULE_PATH, 'test.jar')
@@ -306,6 +444,27 @@
constant.IDE_INTELLIJ, False)
self.assertEqual(mock_project_info.source_path['jar_path'], result_jar)
+ def test_separate_build_target(self):
+ """Test separate_build_target."""
+ test_list = ['1', '22', '333', '4444', '55555', '1', '7777777']
+ target = []
+ sample = [['1', '22', '333'], ['4444'], ['55555', '1'], ['7777777']]
+ for start, end in iter(
+ source_locator._separate_build_targets(test_list, 9)):
+ target.append(test_list[start: end])
+ self.assertEqual(target, sample)
+
+ def test_collect_srcjar_path(self):
+ """Test collect srcjar path."""
+ srcjar_path = 'a/b/aapt2.srcjar'
+ test_module = dict(_MODULE_INFO)
+ test_module['srcjars'] = [srcjar_path]
+ expacted_result = set(['%s!/' % srcjar_path])
+ module_data = source_locator.ModuleData(_MODULE_NAME, test_module,
+ _MODULE_DEPTH)
+ module_data._collect_srcjar_path(srcjar_path)
+ self.assertEqual(module_data.srcjar_paths, expacted_result)
+
if __name__ == '__main__':
unittest.main()
diff --git a/aidegen/run_tests.sh b/aidegen/run_tests.sh
index b3e8008..94914dc 100755
--- a/aidegen/run_tests.sh
+++ b/aidegen/run_tests.sh
@@ -25,6 +25,7 @@
echo -e "${GREEN}All unittests pass${NC}!"
else
echo -e "${RED}Unittest failure found${NC}!"
+ exit 1
fi
}
@@ -39,6 +40,7 @@
PYTHONPATH=$(get_python_path) python3 -m coverage erase
for t in $tests_to_run;
do
+ echo "Test" $t
if ! PYTHONPATH=$(get_python_path) python3 -m coverage run --append --rcfile=$rc_file $t; then
rc=1
echo -e "${RED}$t failed${NC}"
diff --git a/aidegen/templates/eclipse/classpath.xml b/aidegen/templates/eclipse/classpath.xml
new file mode 100644
index 0000000..698a6f6
--- /dev/null
+++ b/aidegen/templates/eclipse/classpath.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+{SRC}
+{LIB}
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+</classpath>
diff --git a/aidegen/templates/eclipse/eclipse.xml b/aidegen/templates/eclipse/eclipse.xml
deleted file mode 100644
index 74735b8..0000000
--- a/aidegen/templates/eclipse/eclipse.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-@SRC@
-@LIB@
-</classpath>
diff --git a/aidegen/templates/eclipse/project.xml b/aidegen/templates/eclipse/project.xml
index 2b19250..a41fa48 100644
--- a/aidegen/templates/eclipse/project.xml
+++ b/aidegen/templates/eclipse/project.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
- <name>@PROJECTNAME@</name>
+ <name>{PROJECTNAME}</name>
<comment></comment>
<projects>
</projects>
@@ -14,4 +14,7 @@
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
+ <linkedResources>
+{LINKEDRESOURCES}
+ </linkedResources>
</projectDescription>
diff --git a/aidegen/templates/enable_debugger/enable_debugger.iml b/aidegen/templates/enable_debugger/enable_debugger.iml
new file mode 100644
index 0000000..3fb3ed8
--- /dev/null
+++ b/aidegen/templates/enable_debugger/enable_debugger.iml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="FacetManager">
+ <facet type="android" name="Android">
+ <configuration>
+ <proGuardCfgFiles />
+ </configuration>
+ </facet>
+ </component>
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
+ </content>
+ <orderEntry type="jdk" jdkName="Android API {API_LEVEL} Platform" jdkType="Android SDK" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module>
diff --git a/aidegen/templates/idea/copyright/Apache_2.xml b/aidegen/templates/idea/copyright/Apache_2.xml
index 1900c4e..e5101e3 100644
--- a/aidegen/templates/idea/copyright/Apache_2.xml
+++ b/aidegen/templates/idea/copyright/Apache_2.xml
@@ -1,7 +1,7 @@
<component name="CopyrightManager">
<copyright>
<option name="notice"
- value="Copyright (C) &#36;today.year 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"/>
+ value="Copyright (C) &#36;today.year 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."/>
<option name="keyword" value="Copyright"/>
<option name="allowReplaceKeyword" value=""/>
<option name="myName" value="Apache 2"/>
diff --git a/aidegen/templates/idea/modules.xml b/aidegen/templates/idea/modules.xml
index 3f99631..a35c26e 100644
--- a/aidegen/templates/idea/modules.xml
+++ b/aidegen/templates/idea/modules.xml
@@ -3,6 +3,7 @@
<component name="ProjectModuleManager">
<modules>
@MODULES@
+@ENABLE_DEBUGGER_MODULE@
</modules>
</component>
</project>
diff --git a/aidegen/templates/jdkTable/jdk.table.xml b/aidegen/templates/jdkTable/jdk.table.xml
deleted file mode 100644
index 38e1289..0000000
--- a/aidegen/templates/jdkTable/jdk.table.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<application>
- <component name="ProjectJdkTable">
- <jdk version="2">
- <name value="JDK18" />
- <type value="JavaSDK" />
- <version value="java version "1.8.0_152"" />
- <homePath value="@JDKpath" />
- <roots>
- <annotationsPath>
- <root type="composite">
- <root url="jar://$APPLICATION_HOME_DIR$/lib/jdkAnnotations.jar!/" type="simple" />
- </root>
- </annotationsPath>
- <classPath>
- <root type="composite">
- <root url="jar://@JDKpath/jre/lib/charsets.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/cldrdata.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/dnsns.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/jaccess.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/localedata.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/nashorn.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/sunec.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/sunjce_provider.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/sunpkcs11.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/zipfs.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/jce.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/jsse.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/management-agent.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/resources.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/rt.jar!/" type="simple" />
- </root>
- </classPath>
- <javadocPath>
- <root type="composite" />
- </javadocPath>
- <sourcePath>
- <root type="composite">
- <root url="jar://@JDKpath/src.zip!/" type="simple" />
- </root>
- </sourcePath>
- </roots>
- <additional />
- </jdk>
- </component>
-</application>
\ No newline at end of file
diff --git a/aidegen/templates/jdkTable/mac.jdk.table.xml b/aidegen/templates/jdkTable/mac.jdk.table.xml
deleted file mode 100644
index 62f05ee..0000000
--- a/aidegen/templates/jdkTable/mac.jdk.table.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<application>
- <component name="ProjectJdkTable">
- <jdk version="2">
- <name value="JDK18" />
- <type value="JavaSDK" />
- <version value="java version "1.8.0_152"" />
- <homePath value="@JDKpath" />
- <roots>
- <annotationsPath>
- <root type="composite">
- <root url="jar://$APPLICATION_HOME_DIR$/lib/jdkAnnotations.jar!/" type="simple" />
- </root>
- </annotationsPath>
- <classPath>
- <root type="composite">
- <root url="jar://@JDKpath/jre/lib/charsets.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/cldrdata.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/dnsns.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/jaccess.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/localedata.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/nashorn.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/sunec.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/sunjce_provider.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/sunpkcs11.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/zipfs.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/jce.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/jsse.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/management-agent.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/resources.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/rt.jar!/" type="simple" />
- <root url="jar://@JDKpath/lib/dt.jar!/" type="simple" />
- <root url="jar://@JDKpath/lib/jconsole.jar!/" type="simple" />
- <root url="jar://@JDKpath/lib/sa-jdi.jar!/" type="simple" />
- <root url="jar://@JDKpath/lib/tools.jar!/" type="simple" />
- </root>
- </classPath>
- <javadocPath>
- <root type="composite" />
- </javadocPath>
- <sourcePath>
- <root type="composite">
- <root url="jar://@JDKpath/src.zip!/" type="simple" />
- </root>
- </sourcePath>
- </roots>
- <additional />
- </jdk>
- </component>
-</application>
\ No newline at end of file
diff --git a/aidegen/templates/jdkTable/part.android.sdk.xml b/aidegen/templates/jdkTable/part.android.sdk.xml
new file mode 100644
index 0000000..305ff39
--- /dev/null
+++ b/aidegen/templates/jdkTable/part.android.sdk.xml
@@ -0,0 +1,23 @@
+ <jdk version="2">
+ <name value="Android API {API_LEVEL} Platform" />
+ <type value="Android SDK" />
+ <version value="java version "1.8.0_152"" />
+ <homePath value="{ANDROID_SDK_PATH}" />
+ <roots>
+ <annotationsPath>
+ <root type="composite" />
+ </annotationsPath>
+ <classPath>
+ <root type="composite">
+ <root url="file://{ANDROID_SDK_PATH}/platforms/android-{API_LEVEL}/data/res" type="simple" />
+ </root>
+ </classPath>
+ <javadocPath>
+ <root type="composite" />
+ </javadocPath>
+ <sourcePath>
+ <root type="composite" />
+ </sourcePath>
+ </roots>
+ <additional jdk="JDK18" sdk="android-{API_LEVEL}" />
+ </jdk>
diff --git a/aidegen/templates/jdkTable/part.jdk.table.xml b/aidegen/templates/jdkTable/part.jdk.table.xml
index 3e7020a..5ac784f 100644
--- a/aidegen/templates/jdkTable/part.jdk.table.xml
+++ b/aidegen/templates/jdkTable/part.jdk.table.xml
@@ -2,7 +2,7 @@
<name value="JDK18" />
<type value="JavaSDK" />
<version value="java version "1.8.0_152"" />
- <homePath value="@JDKpath" />
+ <homePath value="{JDKpath}" />
<roots>
<annotationsPath>
<root type="composite">
@@ -11,21 +11,21 @@
</annotationsPath>
<classPath>
<root type="composite">
- <root url="jar://@JDKpath/jre/lib/charsets.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/cldrdata.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/dnsns.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/jaccess.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/localedata.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/nashorn.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/sunec.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/sunjce_provider.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/sunpkcs11.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/zipfs.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/jce.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/jsse.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/management-agent.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/resources.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/rt.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/charsets.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/ext/cldrdata.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/ext/dnsns.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/ext/jaccess.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/ext/localedata.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/ext/nashorn.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/ext/sunec.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/ext/sunjce_provider.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/ext/sunpkcs11.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/ext/zipfs.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/jce.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/jsse.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/management-agent.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/resources.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/rt.jar!/" type="simple" />
</root>
</classPath>
<javadocPath>
@@ -33,10 +33,9 @@
</javadocPath>
<sourcePath>
<root type="composite">
- <root url="jar://@JDKpath/src.zip!/" type="simple" />
+ <root url="jar://{JDKpath}/src.zip!/" type="simple" />
</root>
</sourcePath>
</roots>
<additional />
</jdk>
- </component>
\ No newline at end of file
diff --git a/aidegen/templates/jdkTable/part.mac.jdk.table.xml b/aidegen/templates/jdkTable/part.mac.jdk.table.xml
index 0c9483b..9a2a1d4 100644
--- a/aidegen/templates/jdkTable/part.mac.jdk.table.xml
+++ b/aidegen/templates/jdkTable/part.mac.jdk.table.xml
@@ -2,7 +2,7 @@
<name value="JDK18" />
<type value="JavaSDK" />
<version value="java version "1.8.0_152"" />
- <homePath value="@JDKpath" />
+ <homePath value="{JDKpath}" />
<roots>
<annotationsPath>
<root type="composite">
@@ -11,28 +11,28 @@
</annotationsPath>
<classPath>
<root type="composite">
- <root url="jar://@JDKpath/jre/lib/charsets.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/cldrdata.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/dnsns.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/jaccess.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/localedata.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/nashorn.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/sunec.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/sunjce_provider.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/sunpkcs11.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/ext/zipfs.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/jce.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/jsse.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/management-agent.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/resources.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/rt.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/management-agent.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/resources.jar!/" type="simple" />
- <root url="jar://@JDKpath/jre/lib/rt.jar!/" type="simple" />
- <root url="jar://@JDKpath/lib/dt.jar!/" type="simple" />
- <root url="jar://@JDKpath/lib/jconsole.jar!/" type="simple" />
- <root url="jar://@JDKpath/lib/sa-jdi.jar!/" type="simple" />
- <root url="jar://@JDKpath/lib/tools.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/charsets.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/ext/cldrdata.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/ext/dnsns.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/ext/jaccess.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/ext/localedata.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/ext/nashorn.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/ext/sunec.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/ext/sunjce_provider.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/ext/sunpkcs11.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/ext/zipfs.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/jce.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/jsse.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/management-agent.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/resources.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/rt.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/management-agent.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/resources.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/jre/lib/rt.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/lib/dt.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/lib/jconsole.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/lib/sa-jdi.jar!/" type="simple" />
+ <root url="jar://{JDKpath}/lib/tools.jar!/" type="simple" />
</root>
</classPath>
<javadocPath>
@@ -40,10 +40,9 @@
</javadocPath>
<sourcePath>
<root type="composite">
- <root url="jar://@JDKpath/src.zip!/" type="simple" />
+ <root url="jar://{JDKpath}/src.zip!/" type="simple" />
</root>
</sourcePath>
</roots>
<additional />
</jdk>
- </component>
\ No newline at end of file
diff --git a/aidegen/templates/module-template.iml b/aidegen/templates/module-template.iml
index 5dd867b..248d56b 100644
--- a/aidegen/templates/module-template.iml
+++ b/aidegen/templates/module-template.iml
@@ -1,9 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
+<!-- Deprecated, please refer to the definition of constant.FILE_IML, and leave
+ this file for backward compatibility.
+ TODO: Remove this file when the CL 1012154 is merged in AIDEGen prebuilt.
+-->
<module type="JAVA_MODULE" version="4">
@FACETS@
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
@SOURCES@
+@SRCJAR@
<orderEntry type="sourceFolder" forTests="false" />
@MODULE_DEPENDENCIES@
<orderEntry type="inheritedJdk" />
diff --git a/aidegen/test_data/Android/Sdk/platforms/android-28/.gitignore b/aidegen/test_data/Android/Sdk/platforms/android-28/.gitignore
new file mode 100644
index 0000000..5e7d273
--- /dev/null
+++ b/aidegen/test_data/Android/Sdk/platforms/android-28/.gitignore
@@ -0,0 +1,4 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore
diff --git a/aidegen/test_data/android_facet.iml b/aidegen/test_data/android_facet.iml
index 0a8d190..561adf7 100644
--- a/aidegen/test_data/android_facet.iml
+++ b/aidegen/test_data/android_facet.iml
@@ -6,6 +6,7 @@
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
@SOURCES@
+@SRCJAR@
<orderEntry type="sourceFolder" forTests="false" />
@MODULE_DEPENDENCIES@
<orderEntry type="inheritedJdk" />
diff --git a/aidegen/test_data/dependencies.iml b/aidegen/test_data/dependencies.iml
index a22917c..19bc2ac 100644
--- a/aidegen/test_data/dependencies.iml
+++ b/aidegen/test_data/dependencies.iml
@@ -9,6 +9,7 @@
<content url="file:///aosp/test_data/project/level11/level22/level31">
<sourceFolder url="file:///aosp/test_data/project/level11/level22/level31" isTestSource="False" />
</content>
+@SRCJAR@
<orderEntry type="sourceFolder" forTests="false" />
@MODULE_DEPENDENCIES@
diff --git a/aidegen/test_data/eclipse.project b/aidegen/test_data/eclipse.project
new file mode 100644
index 0000000..819e1dd
--- /dev/null
+++ b/aidegen/test_data/eclipse.project
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>test</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+ <linkedResources>
+ <link><name>dependencies/module/path</name><type>2</type><location>/android/root/module/path</location></link>
+
+ </linkedResources>
+</projectDescription>
diff --git a/aidegen/test_data/enable_debugger.iml b/aidegen/test_data/enable_debugger.iml
new file mode 100644
index 0000000..fca1692
--- /dev/null
+++ b/aidegen/test_data/enable_debugger.iml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="FacetManager">
+ <facet type="android" name="Android">
+ <configuration>
+ <proGuardCfgFiles />
+ </configuration>
+ </facet>
+ </component>
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
+ </content>
+ <orderEntry type="jdk" jdkName="Android API 28 Platform" jdkType="Android SDK" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module>
diff --git a/aidegen/test_data/jdk_table_xml/android_sdk.xml b/aidegen/test_data/jdk_table_xml/android_sdk.xml
new file mode 100644
index 0000000..b1a8ba6
--- /dev/null
+++ b/aidegen/test_data/jdk_table_xml/android_sdk.xml
@@ -0,0 +1,27 @@
+<application>
+ <component name="ProjectJdkTable">
+ <jdk version="2">
+ <name value="Android API 28 Platform" />
+ <type value="Android SDK" />
+ <version value="java version "1.8.0_152"" />
+ <homePath value="/path/to/Android/Sdk" />
+ <roots>
+ <annotationsPath>
+ <root type="composite" />
+ </annotationsPath>
+ <classPath>
+ <root type="composite">
+ <root url="file:///path/to/Android/Sdk/platforms/android-28/data/res" type="simple" />
+ </root>
+ </classPath>
+ <javadocPath>
+ <root type="composite" />
+ </javadocPath>
+ <sourcePath>
+ <root type="composite" />
+ </sourcePath>
+ </roots>
+ <additional jdk="JDK18" sdk="android-28" />
+ </jdk>
+ </component>
+</application>
diff --git a/aidegen/test_data/jdk_table_xml/android_sdk_nonexistent.xml b/aidegen/test_data/jdk_table_xml/android_sdk_nonexistent.xml
new file mode 100644
index 0000000..1619b6c
--- /dev/null
+++ b/aidegen/test_data/jdk_table_xml/android_sdk_nonexistent.xml
@@ -0,0 +1,27 @@
+<application>
+ <component name="ProjectJdkTable">
+ <jdk version="2">
+ <name value="Android API 28 Platform" />
+ <type value="Fake Android SDK" />
+ <version value="java version "1.8.0_152"" />
+ <homePath value="/path/to/Android/Sdk" />
+ <roots>
+ <annotationsPath>
+ <root type="composite" />
+ </annotationsPath>
+ <classPath>
+ <root type="composite">
+ <root url="file:///path/to/Android/Sdk/platforms/android-28/data/res" type="simple" />
+ </root>
+ </classPath>
+ <javadocPath>
+ <root type="composite" />
+ </javadocPath>
+ <sourcePath>
+ <root type="composite" />
+ </sourcePath>
+ </roots>
+ <additional jdk="JDK18" sdk="android-28" />
+ </jdk>
+ </component>
+</application>
diff --git a/aidegen/test_data/jdk_table_xml/jdk18.xml b/aidegen/test_data/jdk_table_xml/jdk18.xml
new file mode 100644
index 0000000..ea9a52e
--- /dev/null
+++ b/aidegen/test_data/jdk_table_xml/jdk18.xml
@@ -0,0 +1,45 @@
+<application>
+ <component name="ProjectJdkTable">
+ <jdk version="2">
+ <name value="JDK18" />
+ <type value="JavaSDK" />
+ <version value="java version "1.8.0_152"" />
+ <homePath value="/path/to/android/root/prebuilts/jdk/jdk8/linux-x86" />
+ <roots>
+ <annotationsPath>
+ <root type="composite">
+ <root url="jar://$APPLICATION_HOME_DIR$/lib/jdkAnnotations.jar!/" type="simple" />
+ </root>
+ </annotationsPath>
+ <classPath>
+ <root type="composite">
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/charsets.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/cldrdata.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/dnsns.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/jaccess.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/localedata.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/nashorn.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/sunec.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/sunjce_provider.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/sunpkcs11.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/zipfs.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/jce.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/jsse.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/management-agent.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/resources.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/rt.jar!/" type="simple" />
+ </root>
+ </classPath>
+ <javadocPath>
+ <root type="composite" />
+ </javadocPath>
+ <sourcePath>
+ <root type="composite">
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/src.zip!/" type="simple" />
+ </root>
+ </sourcePath>
+ </roots>
+ <additional />
+ </jdk>
+ </component>
+</application>
diff --git a/aidegen/test_data/jdk_table_xml/jdk_nonexistent.xml b/aidegen/test_data/jdk_table_xml/jdk_nonexistent.xml
new file mode 100644
index 0000000..2cc65c6
--- /dev/null
+++ b/aidegen/test_data/jdk_table_xml/jdk_nonexistent.xml
@@ -0,0 +1,49 @@
+<application>
+ <component name="ProjectJdkTable">
+ <jdk version="2">
+ <name value="JDK_OTHER" />
+ <type value="JavaSDK" />
+ </jdk>
+ <jdk version="2">
+ <name value="JDK18" />
+ <type value="JavaSDK" />
+ <version value="java version "1.8.0_152"" />
+ <homePath value="/path/to/android/root/prebuilts/jdk/jdk8/linux-x86" />
+ <roots>
+ <annotationsPath>
+ <root type="composite">
+ <root url="jar://$APPLICATION_HOME_DIR$/lib/jdkAnnotations.jar!/" type="simple" />
+ </root>
+ </annotationsPath>
+ <classPath>
+ <root type="composite">
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/charsets.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/cldrdata.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/dnsns.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/jaccess.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/localedata.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/nashorn.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/sunec.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/sunjce_provider.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/sunpkcs11.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/ext/zipfs.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/jce.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/jsse.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/management-agent.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/resources.jar!/" type="simple" />
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/jre/lib/rt.jar!/" type="simple" />
+ </root>
+ </classPath>
+ <javadocPath>
+ <root type="composite" />
+ </javadocPath>
+ <sourcePath>
+ <root type="composite">
+ <root url="jar:///path/to/android/root/prebuilts/jdk/jdk8/linux-x86/src.zip!/" type="simple" />
+ </root>
+ </sourcePath>
+ </roots>
+ <additional />
+ </jdk>
+ </component>
+</application>
diff --git a/aidegen/test_data/module_dependency.iml b/aidegen/test_data/module_dependency.iml
index 7914c00..5e5bf43 100644
--- a/aidegen/test_data/module_dependency.iml
+++ b/aidegen/test_data/module_dependency.iml
@@ -4,6 +4,7 @@
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
@SOURCES@
+@SRCJAR@
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="inheritedJdk" />
diff --git a/aidegen/test_data/modules.xml b/aidegen/test_data/modules.xml
index 101b7b9..c591862 100644
--- a/aidegen/test_data/modules.xml
+++ b/aidegen/test_data/modules.xml
@@ -3,7 +3,8 @@
<component name="ProjectModuleManager">
<modules>
<module fileurl="file:///$PROJECT_DIR$/android_project.iml" filepath="$PROJECT_DIR$/android_project.iml" />
- <module fileurl="file:///$PROJECT_DIR$/dependencies-android_project.iml" filepath="$PROJECT_DIR$/dependencies-android_project.iml" />
+ <module fileurl="file:///$PROJECT_DIR$/dependencies.iml" filepath="$PROJECT_DIR$/dependencies.iml" />
+
</modules>
</component>
</project>
diff --git a/aidegen/test_data/modules_only_self_module.xml b/aidegen/test_data/modules_only_self_module.xml
new file mode 100644
index 0000000..e5fdf25
--- /dev/null
+++ b/aidegen/test_data/modules_only_self_module.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file:///$PROJECT_DIR$/android_project.iml" filepath="$PROJECT_DIR$/android_project.iml" />
+
+ </modules>
+ </component>
+</project>
diff --git a/aidegen/test_data/modules_with_enable_debugger.xml b/aidegen/test_data/modules_with_enable_debugger.xml
new file mode 100644
index 0000000..5421060
--- /dev/null
+++ b/aidegen/test_data/modules_with_enable_debugger.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file:///$PROJECT_DIR$/android_project.iml" filepath="$PROJECT_DIR$/android_project.iml" />
+ <module fileurl="file:///$PROJECT_DIR$/dependencies.iml" filepath="$PROJECT_DIR$/dependencies.iml" />
+ <module fileurl="file:////path/to/enable_debugger/enable_debugger.iml" filepath="/path/to/enable_debugger/enable_debugger.iml" />
+ </modules>
+ </component>
+</project>
diff --git a/aidegen/test_data/out/soong/.intermediates/packages/apps/test_R/R.jar b/aidegen/test_data/out/soong/.intermediates/packages/apps/test_R/R.jar
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/aidegen/test_data/out/soong/.intermediates/packages/apps/test_R/R.jar
diff --git a/aidegen/test_data/out/soong/.intermediates/packages/apps/test_aapt2/aapt2.srcjar b/aidegen/test_data/out/soong/.intermediates/packages/apps/test_aapt2/aapt2.srcjar
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/aidegen/test_data/out/soong/.intermediates/packages/apps/test_aapt2/aapt2.srcjar
diff --git a/aidegen/test_data/packages/apps/test/src/java.java b/aidegen/test_data/packages/apps/test/src/java.java
new file mode 100644
index 0000000..9c57962
--- /dev/null
+++ b/aidegen/test_data/packages/apps/test/src/java.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package tests.packages;
+
+/** Dummy Class file for unit tests. */
+public class SomeClassForTesting {
+ private static final String SOME_DUMMY_VAR = "For testing purposes";
+}
diff --git a/aidegen/test_data/packages/apps/test/src/main/java/com/android/wrong_package.java b/aidegen/test_data/packages/apps/test/src/main/java/com/android/no_package.java
similarity index 100%
rename from aidegen/test_data/packages/apps/test/src/main/java/com/android/wrong_package.java
rename to aidegen/test_data/packages/apps/test/src/main/java/com/android/no_package.java
diff --git a/aidegen/test_data/packages/apps/test/src/tests.packages/java.java b/aidegen/test_data/packages/apps/test/src/tests.packages/java.java
new file mode 100644
index 0000000..9c57962
--- /dev/null
+++ b/aidegen/test_data/packages/apps/test/src/tests.packages/java.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package tests.packages;
+
+/** Dummy Class file for unit tests. */
+public class SomeClassForTesting {
+ private static final String SOME_DUMMY_VAR = "For testing purposes";
+}
diff --git a/aidegen/test_data/packages/apps/test/src/tests.packages/test/java.java b/aidegen/test_data/packages/apps/test/src/tests.packages/test/java.java
new file mode 100644
index 0000000..3b73d9e
--- /dev/null
+++ b/aidegen/test_data/packages/apps/test/src/tests.packages/test/java.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package tests.packages.test;
+
+/** Dummy Class file for unit tests. */
+public class SomeClassForTesting {
+ private static final String SOME_DUMMY_VAR = "For testing purposes";
+}
diff --git a/aidegen/test_data/project_facet.iml b/aidegen/test_data/project_facet.iml
index 2948c63..2378007 100644
--- a/aidegen/test_data/project_facet.iml
+++ b/aidegen/test_data/project_facet.iml
@@ -4,6 +4,7 @@
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
@SOURCES@
+@SRCJAR@
<orderEntry type="sourceFolder" forTests="false" />
@MODULE_DEPENDENCIES@
<orderEntry type="inheritedJdk" />
diff --git a/aidegen/test_data/source.iml b/aidegen/test_data/source.iml
index 115f5b7..c1cc0c9 100644
--- a/aidegen/test_data/source.iml
+++ b/aidegen/test_data/source.iml
@@ -9,6 +9,7 @@
<sourceFolder url="file:///aosp/test_data/project/level12/level22" isTestSource="False" />
</content>
+@SRCJAR@
<orderEntry type="sourceFolder" forTests="false" />
@MODULE_DEPENDENCIES@
<orderEntry type="inheritedJdk" />
diff --git a/aidegen/test_data/srcjar.iml b/aidegen/test_data/srcjar.iml
new file mode 100644
index 0000000..0e54ef2
--- /dev/null
+++ b/aidegen/test_data/srcjar.iml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+@FACETS@
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+@SOURCES@
+ <content url="jar:///aosp/out/aapt2.srcjar!/">
+ <sourceFolder url="jar:///aosp/out/aapt2.srcjar!/" isTestSource="False" />
+ </content>
+ <orderEntry type="sourceFolder" forTests="false" />
+@MODULE_DEPENDENCIES@
+ <orderEntry type="inheritedJdk" />
+ </component>
+</module>
diff --git a/aidegen/test_data/test.iml b/aidegen/test_data/test.iml
index 4085790..ced6e0c 100644
--- a/aidegen/test_data/test.iml
+++ b/aidegen/test_data/test.iml
@@ -12,7 +12,7 @@
</content>
<orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="module" module-name="dependencies-android_project" />
+ <orderEntry type="module" module-name="dependencies" />
<orderEntry type="inheritedJdk" />
</component>
</module>
diff --git a/aidegen/unittest_constants.py b/aidegen/unittest_constants.py
index e68688b..71dfdb1 100644
--- a/aidegen/unittest_constants.py
+++ b/aidegen/unittest_constants.py
@@ -24,10 +24,10 @@
import os
-from aidegen.constant import AIDEGEN_ROOT_PATH as rp
+from aidegen.lib import common_util
# The data below is only for test usage.
-TEST_DATA_PATH = os.path.join(rp, "test_data") # folder test_data path
+TEST_DATA_PATH = os.path.join(common_util.get_aidegen_root_dir(), "test_data")
IDEA_SH_FIND = [
'/opt/intellij-ce-2018.1/bin/idea.sh', '/opt/intellij-ce-2017.2/bin/idea.sh'
] # script path data
@@ -37,3 +37,10 @@
IDEA_SH_FIND_NONE = '' # Neither IntelliJ CE nor UE script exists.
TEST_PATH = 'path'
TEST_MODULE = 'test'
+ANDROID_SOURCE_DICT = {
+ 'test_data/project/level11/level21': True,
+ 'test_data/project/level11/level22/level31': False,
+ 'test_data/project/level12/level22': False,
+}
+JAR_DEP_LIST = ['test1.jar', 'test2.jar']
+ANDROID_PROJECT_PATH = os.path.join(TEST_DATA_PATH, 'android_project')
diff --git a/aidegen_functional_test/Android.bp b/aidegen_functional_test/Android.bp
index 3e9a862..92b4c6a 100644
--- a/aidegen_functional_test/Android.bp
+++ b/aidegen_functional_test/Android.bp
@@ -29,13 +29,15 @@
python_binary_host {
name: "aidegen_functional_test",
+ stem: "aidegen_functional_test-dev",
defaults: ["aidegen_functional_test_default"],
main: "aidegen_functional_test_main.py",
srcs: [
- "aidegen_functional_test_main.py",
+ "**/*.py",
],
libs: [
"aidegen_lib",
"atest_module_info",
+ "asuite_cc_client",
],
}
diff --git a/aidegen_functional_test/aidegen_functional_test_main.py b/aidegen_functional_test/aidegen_functional_test_main.py
index 4c78b10..6257793 100644
--- a/aidegen_functional_test/aidegen_functional_test_main.py
+++ b/aidegen_functional_test/aidegen_functional_test_main.py
@@ -22,26 +22,24 @@
import itertools
import json
import os
+import subprocess
import sys
import xml.etree.ElementTree
import xml.parsers.expat
-import aidegen.lib.errors
-
from aidegen import aidegen_main
-from aidegen.lib.common_util import get_related_paths
-from aidegen.lib.common_util import time_logged
-from atest import constants
+from aidegen.lib import common_util
+from aidegen.lib import errors
from atest import module_info
-from atest import atest_utils
-_ANDROID_ROOT_PATH = os.environ.get(constants.ANDROID_BUILD_TOP)
-_ROOT_DIR = os.path.join(_ANDROID_ROOT_PATH,
+
+_ROOT_DIR = os.path.join(common_util.get_android_root_dir(),
'tools/asuite/aidegen_functional_test')
_TEST_DATA_PATH = os.path.join(_ROOT_DIR, 'test_data')
_ANDROID_SINGLE_PROJECT_JSON = os.path.join(_TEST_DATA_PATH,
'single_module.json')
_VERIFY_COMMANDS_JSON = os.path.join(_TEST_DATA_PATH, 'verify_commands.json')
+_VERIFY_BINARY_JSON = os.path.join(_TEST_DATA_PATH, 'verify_binary_upload.json')
_PRODUCT_DIR = '$PROJECT_DIR$'
_ANDROID_COMMON = 'android_common'
_LINUX_GLIBC_COMMON = 'linux_glibc_common'
@@ -67,12 +65,12 @@
args: A list of arguments.
Returns:
- An argparse.Namespace class instance holding parsed args.
+ An argparse.Namespace object holding parsed args.
"""
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter,
- usage='aidegen_functional_test [-c | -v]')
+ usage='aidegen_functional_test [-c | -u | -b] -v -r')
group = parser.add_mutually_exclusive_group()
parser.required = False
group.add_argument(
@@ -82,12 +80,27 @@
dest='create_sample',
help=('Create aidegen project files and write data to sample json file '
'for aidegen_functional_test to compare.'))
- group.add_argument(
+ parser.add_argument(
'-v',
- '--verify',
+ '--verbose',
action='store_true',
- dest='verify_aidegen',
+ help='Show DEBUG level logging.')
+ parser.add_argument(
+ '-r',
+ '--remove_bp_json',
+ action='store_true',
+ help='Remove module_bp_java_deps.json for each use case test.')
+ group.add_argument(
+ '-u',
+ action='store_true',
+ dest='use_cases_verified',
help='Verify various use cases of executing aidegen.')
+ group.add_argument(
+ '-b',
+ action='store_true',
+ dest='binary_upload_verified',
+ help=('Verify aidegen\'s use cases by executing different aidegen '
+ 'commands.'))
return parser.parse_args(args)
@@ -96,6 +109,9 @@
Args:
filename: The input project file name.
+
+ Returns:
+ A dictionary contains json data.
"""
data = {}
try:
@@ -103,11 +119,13 @@
data[_SRCS] = []
root = tree.getroot()
for element in root.iter('sourceFolder'):
- src = element.get(_URL).replace(_ANDROID_ROOT_PATH, _PRODUCT_DIR)
+ src = element.get(_URL).replace(common_util.get_android_root_dir(),
+ _PRODUCT_DIR)
data[_SRCS].append(src)
data[_JARS] = []
for element in root.iter('root'):
- jar = element.get(_URL).replace(_ANDROID_ROOT_PATH, _PRODUCT_DIR)
+ jar = element.get(_URL).replace(common_util.get_android_root_dir(),
+ _PRODUCT_DIR)
data[_JARS].append(jar)
except (EnvironmentError, ValueError, LookupError,
xml.parsers.expat.ExpatError) as err:
@@ -117,12 +135,16 @@
def _generate_sample_json():
- """Generate sample iml data and write into a json file."""
+ """Generate sample iml data from a iml file into a dictionary.
+
+ Returns:
+ A dictionary contains sample iml data.
+ """
atest_module_info = module_info.ModuleInfo()
data = {}
for target, filelist in _TEST_IML_DICT.items():
aidegen_main.main([target, '-n'])
- _, abs_path = get_related_paths(atest_module_info, target)
+ _, abs_path = common_util.get_related_paths(atest_module_info, target)
for filename in filelist:
real_iml_file = os.path.join(abs_path, filename)
item_name = os.path.basename(real_iml_file)
@@ -153,15 +175,15 @@
if set(s_items) != set(r_items):
diff_iter = _compare_content(name, item, s_items, r_items)
if diff_iter:
- print('\n%s\n%s' % (atest_utils.colorize(
- 'Test error...', constants.RED), _TEST_ERROR %
- (name, item)))
+ print(
+ '\n%s\n%s' % (common_util.COLORED_FAIL('Test error...'),
+ _TEST_ERROR % (name, item)))
print('%s %s contents are different:' % (name, item))
for diff in diff_iter:
print(diff)
test_successful = False
if test_successful:
- print(atest_utils.colorize(_ALL_PASS, constants.GREEN))
+ print(common_util.COLORED_PASS(_ALL_PASS))
def _compare_content(module_name, item_type, s_items, r_items):
@@ -209,6 +231,10 @@
def _compare_jars_content(module_name, s_items, r_items, msg):
"""Compare src or jar files' data of two dictionaries.
+ AIDEGen treats the jars in folder names 'linux_glib_common' and
+ 'android_common' as the same content. If the paths are different only
+ because of these two names, we ignore it.
+
Args:
module_name: the test module name.
s_items: sample jars' items.
@@ -231,29 +257,80 @@
# pylint: disable=broad-except
# pylint: disable=eval-used
-@time_logged
-def _verify_aidegen():
- """Verify various use cases of executing aidegen."""
- with open(_VERIFY_COMMANDS_JSON, 'r') as jsfile:
- data = json.load(jsfile)
+@common_util.back_to_cwd
+@common_util.time_logged
+def _verify_aidegen(verified_file_path, forced_remove_bp_json):
+ """Verify various use cases of executing aidegen.
+
+ There are two types of running commands:
+ 1. Use 'eval' to run the commands for present codes in aidegen_main.py,
+ such as:
+ aidegen_main.main(['tradefed', '-n', '-v'])
+ 2. Use 'subprocess.check_call' to run the commands for the binary codes of
+ aidegen such as:
+ aidegen tradefed -n -v
+
+ Remove module_bp_java_deps.json in the beginning of running use cases. If
+ users need to remove module_bp_java_deps.json between each use case they
+ can set forced_remove_bp_json true.
+
+ args:
+ verified_file_path: The json file path to be verified.
+ forced_remove_bp_json: Remove module_bp_java_deps.json for each use case
+ test.
+
+ raises:
+ There are two type of exceptions:
+ 1. aidegen.lib.errors for projects' or modules' issues such as,
+ ProjectPathNotExistError.
+ 2. Any exceptions other than aidegen.lib.errors such as,
+ subprocess.CalledProcessError.
+ """
+ os.chdir(common_util.get_android_root_dir())
+ bp_json_path = common_util.get_blueprint_json_path()
+ use_eval = (verified_file_path == _VERIFY_COMMANDS_JSON)
+ try:
+ with open(verified_file_path, 'r') as jsfile:
+ data = json.load(jsfile)
+ except IOError as err:
+ raise errors.JsonFileNotExistError(
+ '%s does not exist, error: %s.' % (verified_file_path, err))
+
+ try:
+ subprocess.check_call(
+ ['build/soong/soong_ui.bash --make-mode clean', '-j'],
+ shell=True)
+ except subprocess.CalledProcessError:
+ print('"make clean" command failed.')
+ raise
+
for use_case in data:
+ print('Use case "{}" is running.'.format(use_case))
+ if forced_remove_bp_json and os.path.exists(bp_json_path):
+ os.remove(bp_json_path)
for cmd in data[use_case]:
+ print('Command "{}" is running.'.format(cmd))
try:
- eval(cmd)
- except (aidegen.lib.errors.ProjectOutsideAndroidRootError,
- aidegen.lib.errors.ProjectPathNotExistError,
- aidegen.lib.errors.NoModuleDefinedInModuleInfoError,
- aidegen.lib.errors.IDENotExistError) as err:
- print('{} command has raise error: {}.'.format(use_case, err))
- except Exception as exp:
- print('{}.{} command {}.'.format(
- use_case, cmd,
- atest_utils.colorize('executes failed', constants.RED)))
- raise Exception(
- 'Unexpected command {} exception {}.'.format(use_case, exp))
- print('{} command {}!'.format(
- use_case, atest_utils.colorize('test passed', constants.GREEN)))
- print(atest_utils.colorize(_ALL_PASS, constants.GREEN))
+ if use_eval:
+ eval(cmd)
+ else:
+ subprocess.check_call(cmd, shell=True)
+ except (errors.ProjectOutsideAndroidRootError,
+ errors.ProjectPathNotExistError,
+ errors.NoModuleDefinedInModuleInfoError,
+ errors.IDENotExistError) as err:
+ print('"{}" raises error: {}.'.format(use_case, err))
+ raise
+ except BaseException:
+ exc_type, _, _ = sys.exc_info()
+ print('"{}.{}" command {}.'.format(
+ use_case, cmd, common_util.COLORED_FAIL('executes failed')))
+ raise BaseException(
+ 'Unexpected command "{}" exception: {}.'.format(
+ use_case, exc_type))
+ print('"{}" command {}!'.format(
+ use_case, common_util.COLORED_PASS('test passed')))
+ print(common_util.COLORED_PASS(_ALL_PASS))
def main(argv):
@@ -265,10 +342,13 @@
argv: A list of system arguments.
"""
args = _parse_args(argv)
+ common_util.configure_logging(args.verbose)
if args.create_sample:
_create_sample_json_file()
- elif args.verify_aidegen:
- _verify_aidegen()
+ elif args.use_cases_verified:
+ _verify_aidegen(_VERIFY_COMMANDS_JSON, args.remove_bp_json)
+ elif args.binary_upload_verified:
+ _verify_aidegen(_VERIFY_BINARY_JSON, args.remove_bp_json)
else:
test_some_sample_iml()
diff --git a/aidegen_functional_test/test_data/verify_binary_upload.json b/aidegen_functional_test/test_data/verify_binary_upload.json
new file mode 100644
index 0000000..b3fc26d
--- /dev/null
+++ b/aidegen_functional_test/test_data/verify_binary_upload.json
@@ -0,0 +1,14 @@
+{
+ "test whole android tree": ["aidegen -n"],
+ "test whole android tree with -a": ["aidegen -a -n -s"],
+ "test whole android tree with frameworks/base -a": ["aidegen frameworks/base -a -n -s"],
+ "test whole android tree in frameworks/base with -a": [
+ "cd frameworks/base",
+ "aidegen -a -n -s",
+ "cd ../.."
+ ],
+ "test Settings framework": ["aidegen Settings framework -n -s"],
+ "test framework launch Android Studio": ["aidegen framework -i s -n -s"],
+ "test framework launch Eclipse": ["aidegen framework -i e -n -s"],
+ "test help": ["aidegen -h"]
+}
diff --git a/aidegen_functional_test/test_data/verify_commands.json b/aidegen_functional_test/test_data/verify_commands.json
index 1ec215e..88da032 100644
--- a/aidegen_functional_test/test_data/verify_commands.json
+++ b/aidegen_functional_test/test_data/verify_commands.json
@@ -1,69 +1,68 @@
{
"aidegen tradefed": [
- "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
"aidegen_main.main(['tradefed', '-n', '-v'])"
],
"aidegen tools/tradefederation/core": [
"aidegen_main.main(['tools/tradefederation/core', '-n', '-v'])"
],
"cd tools/tradefederation/core;aidegen": [
- "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
"os.chdir('tools/tradefederation/core')",
"aidegen_main.main(['-n', '-v'])",
"os.chdir('../../..')"
],
"cd tools/tradefederation;aidegen": [
- "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
"os.chdir('tools/tradefederation')",
"aidegen_main.main(['-n', '-v'])",
"os.chdir('../..')"
],
"aidegen tradefed cts-tradefed": [
- "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
"aidegen_main.main(['tradefed', 'cts-tradefed', '-n', '-v'])"
],
"aidegen tradefed test/suite_harness/tools/cts-tradefed": [
- "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
"aidegen_main.main(['tradefed', 'test/suite_harness/tools/cts-tradefed', '-n', '-v'])"
],
"aidegen tools/tradefederation/core test/suite_harness/tools/cts-tradefed": [
- "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
"aidegen_main.main(['tools/tradefederation/core', 'test/suite_harness/tools/cts-tradefed', '-n', '-v'])"
],
"aidegen cts frameworks/base frameworks/native": [
- "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
"aidegen_main.main(['cts', 'frameworks/base', 'frameworks/native', '-n', '-v'])"
],
"aidegen nonexist/lib/path": [
- "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
"aidegen_main.main(['nonexist/lib/path', '-n', '-v'])"
],
"aidegen no_module_defined": [
- "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
"aidegen_main.main(['no_module_defined', '-n', '-v'])"
],
"aidegen tradefed -p /opt/": [
- "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
"aidegen_main.main(['tradefed', '-p', '/opt/', '-n', '-v'])"
],
"aidegen tradefed -p /home/": [
- "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
"aidegen_main.main(['tradefed', '-p', '/home/', '-n', '-v'])"
],
"aidegen tradefed -i s": [
- "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
"aidegen_main.main(['tradefed', '-i', 's', '-n', '-v'])"
],
"aidegen tradefed -i e": [
- "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
"aidegen_main.main(['tradefed', '-i', 'e', '-n', '-v'])"
],
"aidegen tradefed -s": [
- "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
"aidegen_main.main(['tradefed', '-s', '-n', '-v'])"
],
"aidegen(whole aosp)": [
- "os.remove('out/soong/module_bp_java_deps.json') if os.path.exists('out/soong/module_bp_java_deps.json') else None",
"aidegen_main.main(['-n', '-v'])"
+ ],
+ "aidegen(whole aosp) with -a": [
+ "aidegen_main.main(['-n', '-v', '-a'])"
+ ],
+ "aidegen(whole aosp) with tools/tradefederation and -a": [
+ "aidegen_main.main(['tools/tradefederation/core', '-n', '-v', '-a'])"
+ ],
+ "aidegen(whole aosp) in local with -a": [
+ "os.chdir('tools/tradefederation')",
+ "aidegen_main.main(['-n', '-v', '-a'])",
+ "os.chdir('../..')"
+ ],
+ "aidegen -h": [
+ "aidegen_main.main(['-h'])"
]
}
diff --git a/pylintrc b/pylintrc
index 7eedeae..84284ba 100644
--- a/pylintrc
+++ b/pylintrc
@@ -19,3 +19,8 @@
# Maximum number of characters on a single line.
max-line-length=80
+
+[SIMILARITIES]
+
+# Ignore imports when computing similarities.
+ignore-imports=yes