Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions buildozer/default.spec
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,16 @@ fullscreen = 0
# (str) XML file to include as an intent filters in <activity> tag
#android.manifest.intent_filters =

# (str) XML file to include at the end of the <application> tag
#android.manifest.content_providers =

# (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
Expand Down
14 changes: 14 additions & 0 deletions buildozer/targets/android.py
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,20 @@ 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))]

# extra xml resources
add_xml_resources = config.getlist(
'app', 'android.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', '')
Expand Down
241 changes: 201 additions & 40 deletions tests/targets/test_android.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -52,25 +52,107 @@ 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={}):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default mutable arguments is not recommended: https://docs.python-guide.org/writing/gotchas/

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, fixed it (:

"""
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 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()
assert self.target_android._arch == "armeabi-v7a"
assert self.target_android._build_dir.endswith(
".buildozer/android/platform/build-armeabi-v7a"
Expand Down Expand Up @@ -101,6 +183,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
Expand All @@ -120,6 +203,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
Expand All @@ -140,6 +224,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:
Expand All @@ -149,6 +234,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()
Expand Down Expand Up @@ -179,28 +265,8 @@ def test_install_android_sdk(self, platform):

def test_build_package(self):
"""Basic tests for the build_package() method."""
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)]
self.init_target()
m_execute_build_package = self.call_build_package()
assert m_execute_build_package.call_args_list == [
mock.call(
[
Expand All @@ -218,11 +284,106 @@ def test_build_package(self):
]
)
]
assert m_copyfile.call_args_list == [

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('<?xml version="1.0" encoding="utf-8"?>')

self.init_target({
'android.manifest.intent_filters': 'filters.xml'
})

m_execute_build_package = self.call_build_package()

assert m_execute_build_package.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),
[
('--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', os.path.realpath(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('<?xml version="1.0" encoding="utf-8"?>')

self.init_target({
'android.manifest.content_providers': 'providers.xml'
})

m_execute_build_package = self.call_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', os.path.realpath(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('<?xml version="1.0" encoding="utf-8"?>')

self.init_target({'android.add_xml_resources': 'file_paths.xml'})

m_execute_build_package = self.call_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', os.path.realpath(xml_file_path)),
('debug',),
]
)
]