From 93bbf4090d93032561f60ed809be561fd642fcce Mon Sep 17 00:00:00 2001 From: pavelsof Date: Thu, 21 May 2020 13:38:50 +0200 Subject: [PATCH 1/7] Added the android.manifest.content_providers config. --- buildozer/default.spec | 3 +++ buildozer/targets/android.py | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/buildozer/default.spec b/buildozer/default.spec index 8e51d9b3c..a9dd157de 100644 --- a/buildozer/default.spec +++ b/buildozer/default.spec @@ -188,6 +188,9 @@ fullscreen = 0 # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = +# (str) XML file to include at the end of the tag +#android.manifest.content_providers = + # (str) launchMode to set for the main activity #android.manifest.launch_mode = standard diff --git a/buildozer/targets/android.py b/buildozer/targets/android.py index a508e22ed..333573773 100644 --- a/buildozer/targets/android.py +++ b/buildozer/targets/android.py @@ -1138,6 +1138,13 @@ def build_package(self): build_cmd += [("--intent-filters", join(self.buildozer.root_dir, intent_filters))] + # content providers + content_providers = config.getdefault( + 'app', 'android.manifest.content_providers', '') + if content_providers: + build_cmd += [('--content-providers', join(self.buildozer.root_dir, + content_providers))] + # activity launch mode launch_mode = config.getdefault( 'app', 'android.manifest.launch_mode', '') From 7b29281041284230ccb3fcee6c529bb8b461d63a Mon Sep 17 00:00:00 2001 From: pavelsof Date: Thu, 21 May 2020 13:39:14 +0200 Subject: [PATCH 2/7] Added the android.add_xml_resources config. --- buildozer/default.spec | 4 ++++ buildozer/targets/android.py | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/buildozer/default.spec b/buildozer/default.spec index a9dd157de..b15e96906 100644 --- a/buildozer/default.spec +++ b/buildozer/default.spec @@ -194,6 +194,10 @@ fullscreen = 0 # (str) launchMode to set for the main activity #android.manifest.launch_mode = standard +# (list) XML files to add to the src/main/res/xml subdirectory of the build +# such files are usually referenced in the manifest, e.g. to specifiy file paths for a content provider +#android.add_xml_resources = + # (list) Android additional libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so #android.add_libs_armeabi_v7a = libs/android-v7/*.so diff --git a/buildozer/targets/android.py b/buildozer/targets/android.py index 333573773..2c8a076e3 100644 --- a/buildozer/targets/android.py +++ b/buildozer/targets/android.py @@ -1145,6 +1145,13 @@ def build_package(self): build_cmd += [('--content-providers', join(self.buildozer.root_dir, content_providers))] + # extra xml resources + add_xml_resources = config.getlist( + 'app', 'android.manifest.add_xml_resources', []) + for xml_resource in add_xml_resources: + build_cmd += [('--add-xml-resource', join(self.buildozer.root_dir, + xml_resource))] + # activity launch mode launch_mode = config.getdefault( 'app', 'android.manifest.launch_mode', '') From c4b4fdf0c803418a23efee9852702e67414617c5 Mon Sep 17 00:00:00 2001 From: pavelsof Date: Thu, 21 May 2020 15:13:12 +0200 Subject: [PATCH 3/7] Added the android.add_xml_resources config (2). --- buildozer/targets/android.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildozer/targets/android.py b/buildozer/targets/android.py index 2c8a076e3..41fbbe590 100644 --- a/buildozer/targets/android.py +++ b/buildozer/targets/android.py @@ -1147,7 +1147,7 @@ def build_package(self): # extra xml resources add_xml_resources = config.getlist( - 'app', 'android.manifest.add_xml_resources', []) + 'app', 'android.add_xml_resources', []) for xml_resource in add_xml_resources: build_cmd += [('--add-xml-resource', join(self.buildozer.root_dir, xml_resource))] From c0b27e4e4a352c96fd72334f0bd670ab61f58cf4 Mon Sep 17 00:00:00 2001 From: pavelsof Date: Fri, 22 May 2020 18:04:41 +0200 Subject: [PATCH 4/7] Added a few more unit tests for TargetAndroid. --- tests/targets/test_android.py | 206 +++++++++++++++++++++++++++++++--- 1 file changed, 193 insertions(+), 13 deletions(-) diff --git a/tests/targets/test_android.py b/tests/targets/test_android.py index cb5c5c511..cbdb6c89a 100644 --- a/tests/targets/test_android.py +++ b/tests/targets/test_android.py @@ -1,12 +1,12 @@ import os -import sys -import pytest -import codecs import tempfile +from unittest import mock + +import pytest + import buildozer as buildozer_module from buildozer import Buildozer from buildozer.targets.android import TargetAndroid -from unittest import mock def patch_buildozer(method): @@ -52,25 +52,63 @@ def patch_platform(platform): class TestTargetAndroid: + @staticmethod def default_specfile_path(): return os.path.join(os.path.dirname(buildozer_module.__file__), "default.spec") def setup_method(self): - """Creates a temporary spec file containing the content of the default.spec.""" - self.specfile = tempfile.NamedTemporaryFile(suffix=".spec", delete=False) - default_spec = codecs.open(self.default_specfile_path(), encoding="utf-8") - self.specfile.write(default_spec.read().encode("utf-8")) - self.specfile.close() - self.buildozer = Buildozer(filename=self.specfile.name, target="android") - self.target_android = TargetAndroid(self.buildozer) + """ + Create a temporary directory that will contain the spec file and will + serve as the root_dir. + """ + self.temp_dir = tempfile.TemporaryDirectory() def tear_method(self): - """Deletes the temporary spec file.""" - os.unlink(self.specfile.name) + """ + Remove the temporary directory created in self.setup_method. + """ + self.temp_dir.cleanup() + + def init_target(self, options={}): + """ + Create a buildozer.spec file in the temporary directory and init the + Buildozer and TargetAndroid instances. + + The optional argument can be used to overwrite the config options in + the buildozer.spec file, e.g.: + + self.init_target({'title': 'Test App'}) + + will replace line 4 of the default spec file. + """ + spec_path = os.path.join(self.temp_dir.name, 'buildozer.spec') + + with open(TestTargetAndroid.default_specfile_path()) as f: + default_spec = f.readlines() + + spec = [] + for line in default_spec: + if line.strip(): + key = line.split()[0] + + if key.startswith('#'): + key = key[1:] + + if key in options: + line = '{} = {}\n'.format(key, options[key]) + + spec.append(line) + + with open(spec_path, 'w') as f: + f.writelines(spec) + + self.buildozer = Buildozer(filename=spec_path, target='android') + self.target_android = TargetAndroid(self.buildozer) def test_init(self): """Tests init defaults.""" + self.init_target() assert self.target_android._arch == "armeabi-v7a" assert self.target_android._build_dir.endswith( ".buildozer/android/platform/build-armeabi-v7a" @@ -101,6 +139,7 @@ def test_init_positional_buildozer(self): def test_sdkmanager(self): """Tests the _sdkmanager() method.""" + self.init_target() kwargs = {} with patch_buildozer_cmd() as m_cmd, patch_buildozer_cmd_expect() as m_cmd_expect, patch_os_isfile() as m_isfile: m_isfile.return_value = True @@ -120,6 +159,7 @@ def test_sdkmanager(self): def test_check_requirements(self): """Basic tests for the check_requirements() method.""" + self.init_target() assert not hasattr(self.target_android, "adb_cmd") assert not hasattr(self.target_android, "javac_cmd") assert "PATH" not in self.buildozer.environ @@ -140,6 +180,7 @@ def test_check_requirements(self): def test_check_configuration_tokens(self): """Basic tests for the check_configuration_tokens() method.""" + self.init_target() with mock.patch( "buildozer.targets.android.Target.check_configuration_tokens" ) as m_check_configuration_tokens: @@ -149,6 +190,7 @@ def test_check_configuration_tokens(self): @pytest.mark.parametrize("platform", ["linux", "darwin"]) def test_install_android_sdk(self, platform): """Basic tests for the _install_android_sdk() method.""" + self.init_target() with patch_buildozer_file_exists() as m_file_exists, patch_buildozer_download() as m_download: m_file_exists.return_value = True sdk_dir = self.target_android._install_android_sdk() @@ -179,6 +221,7 @@ def test_install_android_sdk(self, platform): def test_build_package(self): """Basic tests for the build_package() method.""" + self.init_target() expected_dist_dir = ( "{buildozer_dir}/android/platform/build-armeabi-v7a/dists/myapp__armeabi-v7a".format( buildozer_dir=self.buildozer.buildozer_dir) @@ -226,3 +269,140 @@ def test_build_package(self): "{bin_dir}/myapp-0.1-armeabi-v7a-debug.apk".format(bin_dir=self.buildozer.bin_dir), ) ] + + def test_build_package_intent_filters(self): + """ + The build_package method should honour the manifest.intent_filters + config option. + """ + filters_path = os.path.join(self.temp_dir.name, 'filters.xml') + + with open(filters_path, 'w') as f: + f.write('') + + self.init_target({ + 'android.manifest.intent_filters': 'filters.xml'}) + + with patch_target_android( + '_update_libraries_references' + ), patch_target_android( + '_generate_whitelist' + ), mock.patch( + 'buildozer.targets.android.TargetAndroid.execute_build_package' + ) as m_execute_build_package, mock.patch( + 'buildozer.targets.android.copyfile' + ), mock.patch( + 'buildozer.targets.android.os.listdir' + ) as m_listdir: + m_listdir.return_value = ['30.0.0-rc2'] + self.target_android.build_package() + + assert m_execute_build_package.call_args_list == [ + mock.call( + [ + ('--name', "'My Application'"), + ('--version', '0.1'), + ('--package', 'org.test.myapp'), + ('--minsdk', '21'), + ('--ndk-api', '21'), + ('--private', '{buildozer_dir}/android/app'.format(buildozer_dir=self.buildozer.buildozer_dir)), + ('--android-entrypoint', 'org.kivy.android.PythonActivity'), + ('--android-apptheme', '@android:style/Theme.NoTitleBar'), + ('--orientation', 'portrait'), + ('--window',), + ('--intent-filters', filters_path), + ('debug',), + ] + ) + ] + + def test_build_package_content_providers(self): + """ + The build_package method should honour the manifest.content_providers + config option. + """ + providers_path = os.path.join(self.temp_dir.name, 'providers.xml') + + with open(providers_path, 'w') as f: + f.write('') + + self.init_target({ + 'android.manifest.content_providers': 'providers.xml'}) + + with patch_target_android( + '_update_libraries_references' + ), patch_target_android( + '_generate_whitelist' + ), mock.patch( + 'buildozer.targets.android.TargetAndroid.execute_build_package' + ) as m_execute_build_package, mock.patch( + 'buildozer.targets.android.copyfile' + ), mock.patch( + 'buildozer.targets.android.os.listdir' + ) as m_listdir: + m_listdir.return_value = ['30.0.0-rc2'] + self.target_android.build_package() + + assert m_execute_build_package.call_args_list == [ + mock.call( + [ + ('--name', "'My Application'"), + ('--version', '0.1'), + ('--package', 'org.test.myapp'), + ('--minsdk', '21'), + ('--ndk-api', '21'), + ('--private', '{buildozer_dir}/android/app'.format(buildozer_dir=self.buildozer.buildozer_dir)), + ('--android-entrypoint', 'org.kivy.android.PythonActivity'), + ('--android-apptheme', '@android:style/Theme.NoTitleBar'), + ('--orientation', 'portrait'), + ('--window',), + ('--content-providers', providers_path), + ('debug',), + ] + ) + ] + + def test_build_package_add_xml_resources(self): + """ + The build_package method should honour the add_xml_resources config + option. + """ + xml_file_path = os.path.join(self.temp_dir.name, 'file_paths.xml') + + with open(xml_file_path, 'w') as f: + f.write('') + + self.init_target({'android.add_xml_resources': 'file_paths.xml'}) + + with patch_target_android( + '_update_libraries_references' + ), patch_target_android( + '_generate_whitelist' + ), mock.patch( + 'buildozer.targets.android.TargetAndroid.execute_build_package' + ) as m_execute_build_package, mock.patch( + 'buildozer.targets.android.copyfile' + ), mock.patch( + 'buildozer.targets.android.os.listdir' + ) as m_listdir: + m_listdir.return_value = ['30.0.0-rc2'] + self.target_android.build_package() + + assert m_execute_build_package.call_args_list == [ + mock.call( + [ + ('--name', "'My Application'"), + ('--version', '0.1'), + ('--package', 'org.test.myapp'), + ('--minsdk', '21'), + ('--ndk-api', '21'), + ('--private', '{buildozer_dir}/android/app'.format(buildozer_dir=self.buildozer.buildozer_dir)), + ('--android-entrypoint', 'org.kivy.android.PythonActivity'), + ('--android-apptheme', '@android:style/Theme.NoTitleBar'), + ('--orientation', 'portrait'), + ('--window',), + ('--add-xml-resource', xml_file_path), + ('debug',), + ] + ) + ] From 6b290adb85e93d6bc5df60ae379147d02e72e532 Mon Sep 17 00:00:00 2001 From: pavelsof Date: Sat, 23 May 2020 11:46:19 +0200 Subject: [PATCH 5/7] Took the liberty of refactoring the TargetAndroid tests a bit more. --- tests/targets/test_android.py | 129 +++++++++++++++------------------- 1 file changed, 55 insertions(+), 74 deletions(-) diff --git a/tests/targets/test_android.py b/tests/targets/test_android.py index cbdb6c89a..b1c09a27f 100644 --- a/tests/targets/test_android.py +++ b/tests/targets/test_android.py @@ -106,6 +106,50 @@ def init_target(self, options={}): self.buildozer = Buildozer(filename=spec_path, target='android') self.target_android = TargetAndroid(self.buildozer) + def call_build_package(self): + """ + Call the build_package() method of the tested TargetAndroid instance, + patching the functions that would otherwise produce side-effects. + + Return the mocked execute_build_package() method of the TargetAndroid + instance so that tests can easily check which command-line arguments + would be passed on to python-for-android's toolchain. + """ + expected_dist_dir = ( + '{buildozer_dir}/android/platform/build-armeabi-v7a/dists/myapp__armeabi-v7a'.format( + buildozer_dir=self.buildozer.buildozer_dir) + ) + + with patch_target_android( + '_update_libraries_references' + ) as m_update_libraries_references, patch_target_android( + '_generate_whitelist' + ) as m_generate_whitelist, mock.patch( + 'buildozer.targets.android.TargetAndroid.execute_build_package' + ) as m_execute_build_package, mock.patch( + 'buildozer.targets.android.copyfile' + ) as m_copyfile, mock.patch( + 'buildozer.targets.android.os.listdir' + ) as m_listdir: + m_listdir.return_value = ['30.0.0-rc2'] + self.target_android.build_package() + + assert m_listdir.call_count == 1 + assert m_update_libraries_references.call_args_list == [ + mock.call(expected_dist_dir) + ] + assert m_generate_whitelist.call_args_list == [mock.call(expected_dist_dir)] + assert m_copyfile.call_args_list == [ + mock.call( + '{expected_dist_dir}/bin/MyApplication-0.1-debug.apk'.format( + expected_dist_dir=expected_dist_dir + ), + '{bin_dir}/myapp-0.1-armeabi-v7a-debug.apk'.format(bin_dir=self.buildozer.bin_dir), + ) + ] + + return m_execute_build_package + def test_init(self): """Tests init defaults.""" self.init_target() @@ -222,28 +266,7 @@ def test_install_android_sdk(self, platform): def test_build_package(self): """Basic tests for the build_package() method.""" self.init_target() - expected_dist_dir = ( - "{buildozer_dir}/android/platform/build-armeabi-v7a/dists/myapp__armeabi-v7a".format( - buildozer_dir=self.buildozer.buildozer_dir) - ) - with patch_target_android( - "_update_libraries_references" - ) as m_update_libraries_references, patch_target_android( - "_generate_whitelist" - ) as m_generate_whitelist, mock.patch( - "buildozer.targets.android.TargetAndroid.execute_build_package" - ) as m_execute_build_package, mock.patch( - "buildozer.targets.android.copyfile" - ) as m_copyfile, mock.patch( - "buildozer.targets.android.os.listdir" - ) as m_listdir: - m_listdir.return_value = ["30.0.0-rc2"] - self.target_android.build_package() - assert m_listdir.call_count == 1 - assert m_update_libraries_references.call_args_list == [ - mock.call(expected_dist_dir) - ] - assert m_generate_whitelist.call_args_list == [mock.call(expected_dist_dir)] + m_execute_build_package = self.call_build_package() assert m_execute_build_package.call_args_list == [ mock.call( [ @@ -261,18 +284,10 @@ def test_build_package(self): ] ) ] - assert m_copyfile.call_args_list == [ - mock.call( - "{expected_dist_dir}/bin/MyApplication-0.1-debug.apk".format( - expected_dist_dir=expected_dist_dir - ), - "{bin_dir}/myapp-0.1-armeabi-v7a-debug.apk".format(bin_dir=self.buildozer.bin_dir), - ) - ] def test_build_package_intent_filters(self): """ - The build_package method should honour the manifest.intent_filters + The build_package() method should honour the manifest.intent_filters config option. """ filters_path = os.path.join(self.temp_dir.name, 'filters.xml') @@ -281,21 +296,10 @@ def test_build_package_intent_filters(self): f.write('') self.init_target({ - 'android.manifest.intent_filters': 'filters.xml'}) + 'android.manifest.intent_filters': 'filters.xml' + }) - with patch_target_android( - '_update_libraries_references' - ), patch_target_android( - '_generate_whitelist' - ), mock.patch( - 'buildozer.targets.android.TargetAndroid.execute_build_package' - ) as m_execute_build_package, mock.patch( - 'buildozer.targets.android.copyfile' - ), mock.patch( - 'buildozer.targets.android.os.listdir' - ) as m_listdir: - m_listdir.return_value = ['30.0.0-rc2'] - self.target_android.build_package() + m_execute_build_package = self.call_build_package() assert m_execute_build_package.call_args_list == [ mock.call( @@ -318,7 +322,7 @@ def test_build_package_intent_filters(self): def test_build_package_content_providers(self): """ - The build_package method should honour the manifest.content_providers + The build_package() method should honour the manifest.content_providers config option. """ providers_path = os.path.join(self.temp_dir.name, 'providers.xml') @@ -327,21 +331,10 @@ def test_build_package_content_providers(self): f.write('') self.init_target({ - 'android.manifest.content_providers': 'providers.xml'}) + 'android.manifest.content_providers': 'providers.xml' + }) - with patch_target_android( - '_update_libraries_references' - ), patch_target_android( - '_generate_whitelist' - ), mock.patch( - 'buildozer.targets.android.TargetAndroid.execute_build_package' - ) as m_execute_build_package, mock.patch( - 'buildozer.targets.android.copyfile' - ), mock.patch( - 'buildozer.targets.android.os.listdir' - ) as m_listdir: - m_listdir.return_value = ['30.0.0-rc2'] - self.target_android.build_package() + m_execute_build_package = self.call_build_package() assert m_execute_build_package.call_args_list == [ mock.call( @@ -364,7 +357,7 @@ def test_build_package_content_providers(self): def test_build_package_add_xml_resources(self): """ - The build_package method should honour the add_xml_resources config + The build_package() method should honour the add_xml_resources config option. """ xml_file_path = os.path.join(self.temp_dir.name, 'file_paths.xml') @@ -374,19 +367,7 @@ def test_build_package_add_xml_resources(self): self.init_target({'android.add_xml_resources': 'file_paths.xml'}) - with patch_target_android( - '_update_libraries_references' - ), patch_target_android( - '_generate_whitelist' - ), mock.patch( - 'buildozer.targets.android.TargetAndroid.execute_build_package' - ) as m_execute_build_package, mock.patch( - 'buildozer.targets.android.copyfile' - ), mock.patch( - 'buildozer.targets.android.os.listdir' - ) as m_listdir: - m_listdir.return_value = ['30.0.0-rc2'] - self.target_android.build_package() + m_execute_build_package = self.call_build_package() assert m_execute_build_package.call_args_list == [ mock.call( From 6fa8df464f5e218ab4cc88168952bc83672dd841 Mon Sep 17 00:00:00 2001 From: pavelsof Date: Sat, 23 May 2020 12:04:09 +0200 Subject: [PATCH 6/7] Fixed the TargetAndroid tests for macOS. --- tests/targets/test_android.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/targets/test_android.py b/tests/targets/test_android.py index b1c09a27f..96781fe27 100644 --- a/tests/targets/test_android.py +++ b/tests/targets/test_android.py @@ -314,7 +314,7 @@ def test_build_package_intent_filters(self): ('--android-apptheme', '@android:style/Theme.NoTitleBar'), ('--orientation', 'portrait'), ('--window',), - ('--intent-filters', filters_path), + ('--intent-filters', os.path.realpath(filters_path)), ('debug',), ] ) @@ -349,7 +349,7 @@ def test_build_package_content_providers(self): ('--android-apptheme', '@android:style/Theme.NoTitleBar'), ('--orientation', 'portrait'), ('--window',), - ('--content-providers', providers_path), + ('--content-providers', os.path.realpath(providers_path)), ('debug',), ] ) @@ -382,7 +382,7 @@ def test_build_package_add_xml_resources(self): ('--android-apptheme', '@android:style/Theme.NoTitleBar'), ('--orientation', 'portrait'), ('--window',), - ('--add-xml-resource', xml_file_path), + ('--add-xml-resource', os.path.realpath(xml_file_path)), ('debug',), ] ) From cdbef769ae2a196d7970d1eb31d6692419054926 Mon Sep 17 00:00:00 2001 From: pavelsof Date: Sat, 23 May 2020 13:10:37 +0200 Subject: [PATCH 7/7] Removed TestTargetAndroid.init_target's mutable argument. --- tests/targets/test_android.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/targets/test_android.py b/tests/targets/test_android.py index 96781fe27..6f86ae844 100644 --- a/tests/targets/test_android.py +++ b/tests/targets/test_android.py @@ -70,7 +70,7 @@ def tear_method(self): """ self.temp_dir.cleanup() - def init_target(self, options={}): + def init_target(self, options=None): """ Create a buildozer.spec file in the temporary directory and init the Buildozer and TargetAndroid instances. @@ -82,6 +82,9 @@ def init_target(self, options={}): will replace line 4 of the default spec file. """ + if options is None: + options = {} + spec_path = os.path.join(self.temp_dir.name, 'buildozer.spec') with open(TestTargetAndroid.default_specfile_path()) as f: