Merge "AIDEGen: increase project_config code coverage"
diff --git a/aidegen/lib/config.py b/aidegen/lib/config.py
index 168831d..0c9950a 100644
--- a/aidegen/lib/config.py
+++ b/aidegen/lib/config.py
@@ -14,12 +14,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-"""Config helper class."""
+"""Config class."""
import copy
import json
import logging
import os
+import re
from aidegen.lib import common_util
@@ -177,3 +178,72 @@
logging.warning(('Can\'t create the enable_debugger module in %s.\n'
'%s'), self._CONFIG_DIR, err)
return False
+
+
+class IdeaProperties():
+ """Class manages IntelliJ's idea.properties attribute.
+
+ Class Attributes:
+ _PROPERTIES_FILE: The property file name of IntelliJ.
+ _KEY_FILESIZE: The key name of the maximun file size.
+ _FILESIZE_LIMIT: The value to be set as the max file size.
+ _RE_SEARCH_FILESIZE: A regular expression to find the current max file
+ size.
+ _PROPERTIES_CONTENT: The default content of idea.properties to be
+ generated.
+
+ Attributes:
+ idea_file: The absolute path of the idea.properties.
+ For example:
+ In Linux, it is ~/.IdeaIC2019.1/config/idea.properties.
+ In Mac, it is ~/Library/Preferences/IdeaIC2019.1/
+ idea.properties.
+ """
+
+ # Constants of idea.properties
+ _PROPERTIES_FILE = 'idea.properties'
+ _KEY_FILESIZE = 'idea.max.intellisense.filesize'
+ _FILESIZE_LIMIT = 100000
+ _RE_SEARCH_FILESIZE = r'%s\s?=\s?(?P<value>\d+)' % _KEY_FILESIZE
+ _PROPERTIES_CONTENT = """# custom IntelliJ IDEA properties
+
+#-------------------------------------------------------------------------------
+# Maximum size of files (in kilobytes) for which IntelliJ IDEA provides coding
+# assistance. Coding assistance for large files can affect editor performance
+# and increase memory consumption.
+# The default value is 2500.
+#-------------------------------------------------------------------------------
+idea.max.intellisense.filesize=100000
+"""
+
+ def __init__(self, config_dir):
+ """IdeaProperties initialize.
+
+ Args:
+ config_dir: The absolute dir of the idea.properties.
+ """
+ self.idea_file = os.path.join(config_dir, self._PROPERTIES_FILE)
+
+ def _set_default_idea_properties(self):
+ """Create the file idea.properties."""
+ common_util.file_generate(self.idea_file, self._PROPERTIES_CONTENT)
+
+ def _reset_max_file_size(self):
+ """Reset the max file size value in the idea.properties."""
+ updated_flag = False
+ properties = common_util.read_file_content(self.idea_file).splitlines()
+ for index, line in enumerate(properties):
+ res = re.search(self._RE_SEARCH_FILESIZE, line)
+ if res and int(res['value']) < self._FILESIZE_LIMIT:
+ updated_flag = True
+ properties[index] = '%s=%s' % (self._KEY_FILESIZE,
+ str(self._FILESIZE_LIMIT))
+ if updated_flag:
+ common_util.file_generate(self.idea_file, '\n'.join(properties))
+
+ def set_max_file_size(self):
+ """Set the max file size parameter in the idea.properties."""
+ if not os.path.exists(self.idea_file):
+ self._set_default_idea_properties()
+ else:
+ self._reset_max_file_size()
diff --git a/aidegen/lib/config_unittest.py b/aidegen/lib/config_unittest.py
index 5491dae..c509f82 100644
--- a/aidegen/lib/config_unittest.py
+++ b/aidegen/lib/config_unittest.py
@@ -16,6 +16,9 @@
"""Unittests for AidegenConfig class."""
+import os
+import shutil
+import tempfile
import unittest
from unittest import mock
@@ -134,5 +137,80 @@
self.assertTrue(mock_makedirs.called)
+class IdeaPropertiesUnittests(unittest.TestCase):
+ """Unit tests for IdeaProperties class."""
+
+ _CONFIG_DIR = None
+
+ def setUp(self):
+ """Prepare the testdata related path."""
+ IdeaPropertiesUnittests._CONFIG_DIR = tempfile.mkdtemp()
+
+ def tearDown(self):
+ """Clear the testdata related path."""
+ shutil.rmtree(IdeaPropertiesUnittests._CONFIG_DIR)
+
+ def test_set_default_properties(self):
+ """Test creating the idea.properties with default content."""
+ cfg = config.IdeaProperties(IdeaPropertiesUnittests._CONFIG_DIR)
+ cfg._set_default_idea_properties()
+ expected_data = cfg._PROPERTIES_CONTENT.format(
+ KEY_FILE_SIZE=cfg._KEY_FILESIZE,
+ VALUE_FILE_SIZE=cfg._FILESIZE_LIMIT)
+ generated_file = os.path.join(IdeaPropertiesUnittests._CONFIG_DIR,
+ cfg._PROPERTIES_FILE)
+ generated_content = common_util.read_file_content(generated_file)
+ self.assertEqual(expected_data, generated_content)
+
+ @mock.patch.object(common_util, 'read_file_content')
+ def test_reset_max_file_size(self, mock_content):
+ """Test reset the file size limit when it's smaller than 100000."""
+ mock_content.return_value = ('# custom IntelliJ IDEA properties\n'
+ 'idea.max.intellisense.filesize=5000')
+ expected_data = ('# custom IntelliJ IDEA properties\n'
+ 'idea.max.intellisense.filesize=100000')
+ cfg = config.IdeaProperties(IdeaPropertiesUnittests._CONFIG_DIR)
+ cfg._reset_max_file_size()
+ generated_file = os.path.join(IdeaPropertiesUnittests._CONFIG_DIR,
+ cfg._PROPERTIES_FILE)
+ with open(generated_file) as properties_file:
+ generated_content = properties_file.read()
+ self.assertEqual(expected_data, generated_content)
+
+ @mock.patch.object(common_util, 'file_generate')
+ @mock.patch.object(common_util, 'read_file_content')
+ def test_no_reset_max_file_size(self, mock_content, mock_gen_file):
+ """Test when the file size is larger than 100000."""
+ mock_content.return_value = ('# custom IntelliJ IDEA properties\n'
+ 'idea.max.intellisense.filesize=110000')
+ cfg = config.IdeaProperties(IdeaPropertiesUnittests._CONFIG_DIR)
+ cfg._reset_max_file_size()
+ self.assertFalse(mock_gen_file.called)
+
+ @mock.patch.object(config.IdeaProperties, '_reset_max_file_size')
+ @mock.patch.object(config.IdeaProperties, '_set_default_idea_properties')
+ @mock.patch('os.path.exists')
+ def test_set_idea_properties_called(self, mock_file_exists,
+ mock_set_default,
+ mock_reset_file_size):
+ """Test _set_default_idea_properties() method is called."""
+ mock_file_exists.return_value = False
+ cfg = config.IdeaProperties(IdeaPropertiesUnittests._CONFIG_DIR)
+ cfg.set_max_file_size()
+ self.assertTrue(mock_set_default.called)
+ self.assertFalse(mock_reset_file_size.called)
+
+ @mock.patch.object(config.IdeaProperties, '_reset_max_file_size')
+ @mock.patch.object(config.IdeaProperties, '_set_default_idea_properties')
+ @mock.patch('os.path.exists')
+ def test_reset_properties_called(self, mock_file_exists, mock_set_default,
+ mock_reset_file_size):
+ """Test _reset_max_file_size() method is called."""
+ mock_file_exists.return_value = True
+ cfg = config.IdeaProperties(IdeaPropertiesUnittests._CONFIG_DIR)
+ cfg.set_max_file_size()
+ self.assertFalse(mock_set_default.called)
+ self.assertTrue(mock_reset_file_size.called)
+
if __name__ == '__main__':
unittest.main()
diff --git a/aidegen/lib/ide_util.py b/aidegen/lib/ide_util.py
index 2a45022..e0d8bbb 100644
--- a/aidegen/lib/ide_util.py
+++ b/aidegen/lib/ide_util.py
@@ -203,6 +203,7 @@
_JDK_PART_TEMPLATE_PATH: The path of the template of partial JDK table.
_SYMBOLIC_VERSIONS: A string list of the symbolic link paths of
IntelliJ.
+ _CONFIG_DIR: A string of the config folder name.
For example:
1. Check if IntelliJ is installed.
@@ -215,6 +216,7 @@
_JDK_PART_TEMPLATE_PATH = ''
_DEFAULT_ANDROID_SDK_PATH = ''
_SYMBOLIC_VERSIONS = []
+ _CONFIG_DIR = ''
def __init__(self, installed_path=None, config_reset=False):
super().__init__(installed_path, config_reset)
@@ -243,6 +245,10 @@
jdk_table.config_jdk_file()
jdk_table.gen_enable_debugger_module(self.project_abspath)
+ # Set the max file size in the idea.properties.
+ intellij_config_dir = os.path.join(_config_path, self._CONFIG_DIR)
+ config.IdeaProperties(intellij_config_dir).set_max_file_size()
+
def _get_config_root_paths(self):
"""Get the config root paths from derived class.
@@ -377,6 +383,7 @@
_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.
+ _CONFIG_DIR = 'config'
_IDE_JDK_TABLE_PATH = 'config/options/jdk.table.xml'
_JDK_PART_TEMPLATE_PATH = os.path.join(
common_util.get_aidegen_root_dir(),
diff --git a/aidegen/lib/project_file_gen.py b/aidegen/lib/project_file_gen.py
index 98af1f0..6df8bc6 100644
--- a/aidegen/lib/project_file_gen.py
+++ b/aidegen/lib/project_file_gen.py
@@ -550,8 +550,6 @@
# 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)):
@@ -646,10 +644,27 @@
projects: A list of ProjectInfo instances.
"""
main_project_absolute_path = projects[0].project_absolute_path
- git_paths = [project.git_path for project in projects]
+ if main_project_absolute_path == common_util.get_android_root_dir():
+ git_paths = list(_get_all_git_path(main_project_absolute_path))
+ else:
+ git_paths = [project.git_path for project in projects]
_write_vcs_xml(main_project_absolute_path, git_paths)
+def _get_all_git_path(root_path):
+ """Traverse all subdirectories to get all git folder's path.
+
+ Args:
+ root_path: A string of path to traverse.
+
+ Yields:
+ A git folder's path.
+ """
+ for dir_path, dir_names, _ in os.walk(root_path):
+ if _GIT_FOLDER_NAME in dir_names:
+ yield dir_path
+
+
def _generate_git_ignore(target_folder):
"""Generate .gitignore file.
diff --git a/aidegen/lib/project_info.py b/aidegen/lib/project_info.py
index 31b4e43..1fd7b9f 100644
--- a/aidegen/lib/project_info.py
+++ b/aidegen/lib/project_info.py
@@ -111,6 +111,7 @@
self.modules_info.get_module_names(rel_path))
self.project_relative_path = rel_path
self.project_absolute_path = abs_path
+ self.git_path = ''
self.iml_path = ''
self._set_default_modues()
self._init_source_path()
@@ -305,7 +306,7 @@
return os.path.basename(abs_path)
return target
- def locate_source(self):
+ def locate_source(self, build=True):
"""Locate the paths of dependent source folders and jar files.
Try to reference source folder path as dependent module unless the
@@ -329,6 +330,10 @@
},
}
+ Args:
+ build: A boolean default to true. If false, skip building jar and
+ srcjar files, otherwise build them.
+
Example usage:
project.source_path = project.locate_source()
E.g.
@@ -352,13 +357,15 @@
self.source_path['srcjar_path'].update(module.srcjar_paths)
self._append_jars_as_dependencies(module)
rebuild_targets.update(module.build_targets)
+ if project_config.ProjectConfig.get_instance().is_skip_build:
+ return
if rebuild_targets:
- if not project_config.ProjectConfig.get_instance().is_skip_build:
+ if build:
verbose = project_config.ProjectConfig.get_instance().verbose
_batch_build_dependencies(verbose, rebuild_targets)
- self.locate_source()
+ self.locate_source(build=False)
else:
- logging.warning('Jar or srcjar files build failed:\n\t%s.',
+ logging.warning('Jar or srcjar files build skipped:\n\t%s.',
'\n\t'.join(rebuild_targets))
def _generate_moduledata(self, module_name, module_data):
diff --git a/aidegen/lib/project_info_unittest.py b/aidegen/lib/project_info_unittest.py
index 0cc3d97..acc774e 100644
--- a/aidegen/lib/project_info_unittest.py
+++ b/aidegen/lib/project_info_unittest.py
@@ -143,9 +143,6 @@
generated_jar = ('out/soong/.intermediates/packages/apps/test/test/'
'android_common/generated.jar')
locate_module_info = dict(unittest_constants.MODULE_INFO)
- locate_module_info['srcs'].extend(
- [('out/soong/.intermediates/packages/apps/test/test/android_common/'
- 'gen/test.srcjar')])
locate_module_info['installed'] = [generated_jar]
mock_module_info.is_module.return_value = True
mock_module_info.get_paths.return_value = [
@@ -165,17 +162,6 @@
project_info_obj.locate_source()
self.assertEqual(project_info_obj.source_path['jar_path'], result_jar)
- # Test on jar exists.
- jar_abspath = os.path.join(test_root_path, generated_jar)
- result_jar = set([generated_jar])
- try:
- open(jar_abspath, 'w').close()
- project_info_obj.locate_source()
- self.assertEqual(project_info_obj.source_path['jar_path'],
- result_jar)
- finally:
- shutil.rmtree(test_root_path)
-
# Test collects source and test folders.
result_source = set(['packages/apps/test/src/main/java'])
result_test = set(['packages/apps/test/tests'])
@@ -184,13 +170,6 @@
self.assertEqual(project_info_obj.source_path['test_folder_path'],
result_test)
- # Test loading jar from dependencies parameter.
- default_jar = os.path.join(unittest_constants.MODULE_PATH, 'test.jar')
- locate_module_info['dependencies'] = [default_jar]
- result_jar = set([generated_jar, default_jar])
- project_info_obj.locate_source()
- self.assertEqual(project_info_obj.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']
@@ -201,6 +180,18 @@
target.append(test_list[start:end])
self.assertEqual(target, sample)
+ @mock.patch.object(project_info.ProjectInfo, 'locate_source')
+ @mock.patch('atest.module_info.ModuleInfo')
+ def test_rebuild_jar_once(self, mock_module_info, mock_locate_source):
+ """Test rebuild the jar/srcjar only one time."""
+ mock_module_info.get_paths.return_value = ['m1']
+ project_info.ProjectInfo.modules_info = mock_module_info
+ proj_info = project_info.ProjectInfo(self.args.module_name, False)
+ proj_info.locate_source(build=False)
+ self.assertEqual(mock_locate_source.call_count, 1)
+ proj_info.locate_source(build=True)
+ self.assertEqual(mock_locate_source.call_count, 2)
+
if __name__ == '__main__':
unittest.main()
diff --git a/aidegen/lib/source_locator.py b/aidegen/lib/source_locator.py
index 649e0e5..a5a6119 100644
--- a/aidegen/lib/source_locator.py
+++ b/aidegen/lib/source_locator.py
@@ -41,14 +41,15 @@
_KEY_JARS = 'jars'
_KEY_TESTS = 'tests'
_NAME_AAPT2 = 'aapt2'
-_TARGET_R_JAR = 'R.jar'
+_TARGET_R_SRCJAR = 'R.srcjar'
_TARGET_AAPT2_SRCJAR = _NAME_AAPT2 + _SRCJAR_EXT
-_TARGET_BUILD_FILES = [_TARGET_AAPT2_SRCJAR, _TARGET_R_JAR]
+_TARGET_BUILD_FILES = [_TARGET_AAPT2_SRCJAR, _TARGET_R_SRCJAR]
_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'
]
+_ANDROID = 'android'
class ModuleData:
@@ -137,14 +138,14 @@
def _collect_r_srcs_paths(self):
"""Collect the source folder of R.java.
- 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,
+ Check if the path of aapt2.srcjar or R.srcjar exists, these are both the
+ values of key "srcjars" in module_data. If neither of the cases exists,
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
+ # TODO(b/135594800): Set aapt2.srcjar or R.srcjar as a dependency in
Eclipse.
"""
if (self._is_app_module() and self._is_target_module() and
@@ -160,15 +161,16 @@
def _collect_srcjar_path(self, srcjar):
"""Collect the source folders from a srcjar path.
- Set the aapt2.srcjar or R.jar as source root:
+ Set the aapt2.srcjar or R.srcjar 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!/.
+ Case R.srcjar:
+ The source path string is out/soong/.../gen/android/R.srcjar.
+ The source content descriptor is
+ out/soong/.../gen/android/R.srcjar!/.
Args:
srcjar: A file path string relative to ANDROID_BUILD_TOP, the build
@@ -196,19 +198,29 @@
def _get_r_dir(srcjar):
"""Get the source folder of R.java for Eclipse.
+ Get the folder contains the R.java of aapt2.srcjar or R.srcjar:
+ Case aapt2.srcjar:
+ If the relative path of the aapt2.srcjar is a/b/aapt2.srcjar, the
+ source root of the R.java is a/b/aapt2
+ Case R.srcjar:
+ If the relative path of the R.srcjar is a/b/android/R.srcjar, the
+ source root of the R.java is a/b/aapt2/R
+
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.
+ file name is not aapt2.srcjar or R.srcjar.
"""
target_folder, target_file = os.path.split(srcjar)
+ base_dirname = os.path.basename(target_folder)
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')
+ if target_file == _TARGET_R_SRCJAR and base_dirname == _ANDROID:
+ return os.path.join(os.path.dirname(target_folder),
+ _NAME_AAPT2, 'R')
return None
def _init_module_path(self):
@@ -253,16 +265,14 @@
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_EXT):
- self._append_jar_from_installed(self.specific_soong_path)
- elif common_util.is_target(src_item, _TARGET_FILES):
+ if common_util.is_target(src_item, _TARGET_FILES):
# Only scan one java file in each source directories.
src_item_dir = os.path.dirname(src_item)
if src_item_dir not in scanned_dirs:
scanned_dirs.add(src_item_dir)
src_dir = self._get_source_folder(src_item)
else:
- # To record what files except java and srcjar in the srcs.
+ # To record what files except java and kt in the srcs.
logging.debug('%s is not in parsing scope.', src_item)
if src_dir:
self._add_to_source_or_test_dirs(src_dir)
@@ -430,6 +440,10 @@
directly. There is only jar file name in self.module_data['jars'], it
has to be combined with self.module_data['path'] to append into
self.jar_files.
+ Once the file doesn't exist, it's not assumed to be a prebuilt jar so
+ that we can ignore it.
+ # TODO(b/141959125): Collect the correct prebuilt jar files by jdeps.go.
+
For example:
'asm-6.0': {
'jars': [
@@ -453,7 +467,8 @@
rel_path = self._get_jar_path_from_prebuilts(jar_name)
if rel_path:
jar_path = rel_path
- self._append_jar_file(jar_path)
+ if os.path.exists(common_util.get_abs_path(jar_path)):
+ self._append_jar_file(jar_path)
@staticmethod
def _get_jar_path_from_prebuilts(jar_name):
diff --git a/aidegen/lib/source_locator_unittest.py b/aidegen/lib/source_locator_unittest.py
index dde8e76..c97c1e2 100644
--- a/aidegen/lib/source_locator_unittest.py
+++ b/aidegen/lib/source_locator_unittest.py
@@ -91,13 +91,19 @@
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'
+ # Test for R.srcjar
+ test_r_jar = 'b/android/R.srcjar'
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 the R.srcjar is not under the android folder.
+ test_wrong_r_jar = 'b/test/R.srcjar'
+ expect_result = None
+ r_dir = module_data._get_r_dir(test_wrong_r_jar)
+ self.assertEqual(r_dir, expect_result)
+
+ # Test for the target file is not aapt2.srcjar or R.srcjar
test_unknown_target = 'c/proto.srcjar'
expect_result = None
r_dir = module_data._get_r_dir(test_unknown_target)
@@ -258,11 +264,13 @@
os.path.join(unittest_constants.MODULE_PATH,
'tests/test_second.jar')
])
+ result_missing_jars = set()
mock_android_root_dir.return_value = unittest_constants.TEST_DATA_PATH
module_data = source_locator.ModuleData(unittest_constants.TEST_MODULE,
module_info, 0)
module_data._set_jars_jarfile()
self.assertEqual(module_data.jar_files, result_jar_list)
+ self.assertEqual(module_data.missing_jars, result_missing_jars)
@mock.patch('aidegen.lib.common_util.get_android_root_dir')
def test_locate_sources_path(self, mock_android_root_dir):
@@ -293,25 +301,6 @@
module_data.locate_sources_path()
self.assertEqual(module_data.jar_files, result_jar_list)
- # Test find jar by srcjar.
- module_info = dict(unittest_constants.MODULE_INFO)
- module_info['srcs'].extend(
- [('out/soong/.intermediates/packages/apps/test/test/android_common/'
- 'gen/test.srcjar')])
- module_info['installed'] = [
- ('out/soong/.intermediates/packages/apps/test/test/android_common/'
- 'test.jar')
- ]
- result_jar_list = set([
- jar_file,
- ('out/soong/.intermediates/packages/apps/test/test/'
- 'android_common/test.jar')
- ])
- module_data = source_locator.ModuleData(unittest_constants.TEST_MODULE,
- module_info, 0)
- module_data.locate_sources_path()
- self.assertEqual(module_data.jar_files, result_jar_list)
-
@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."""
diff --git a/aidegen/unittest_constants.py b/aidegen/unittest_constants.py
index e24d865..557d301 100644
--- a/aidegen/unittest_constants.py
+++ b/aidegen/unittest_constants.py
@@ -50,7 +50,6 @@
'srcs': [
'packages/apps/test/src/main/java/com/android/java.java',
'packages/apps/test/tests/com/android/test.java',
- 'packages/apps/test/tests/test.srcjar'
],
'dependencies': [],
'installed': []