Skip to content

Commit

Permalink
Add aab (Android App Bundle) support
Browse files Browse the repository at this point in the history
  • Loading branch information
misl6 committed Oct 24, 2021
1 parent 7cffb89 commit 98a9b27
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 75 deletions.
12 changes: 8 additions & 4 deletions buildozer/default.spec
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ fullscreen = 0
# (int) Target Android API, should be as high as possible.
#android.api = 27

# (int) Minimum API your APK will support.
# (int) Minimum API your APK / AAB will support.
#android.minapi = 21

# (int) Android SDK version to use
Expand Down Expand Up @@ -253,8 +253,9 @@ fullscreen = 0
# (bool) Copy library instead of making a libpymodules.so
#android.copy_libs = 1

# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
android.arch = armeabi-v7a
# (list) The Android archs to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
# In past, was `android.arch` as we weren't supporting builds for multiple archs at the same time.
android.archs = arm64-v8a, armeabi-v7a

# (int) overrides automatic versionCode computation (used in build.gradle)
# this is not the same as app version and should only be edited if you know what you're doing
Expand All @@ -275,6 +276,9 @@ android.allow_backup = True
# (bool) disables the compilation of py to pyc/pyo files when packaging
# android.no-compile-pyo = True

# (str) The format used to package the app for release mode (aab or apk).
# android.release_artifact = aab

#
# Python for android (p4a) specific
#
Expand Down Expand Up @@ -374,7 +378,7 @@ warn_on_root = 1
# (str) Path to build artifact storage, absolute or relative to spec file
# build_dir = ./.buildozer

# (str) Path to build output (i.e. .apk, .ipa) storage
# (str) Path to build output (i.e. .apk, .aab, .ipa) storage
# bin_dir = ./bin

# -----------------------------------------------------------------------------
Expand Down
3 changes: 3 additions & 0 deletions buildozer/target.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Target:
def __init__(self, buildozer):
self.buildozer = buildozer
self.build_mode = 'debug'
self.artifact_format = 'apk'
self.platform_update = False

def check_requirements(self):
Expand Down Expand Up @@ -101,6 +102,7 @@ def cmd_update(self, *args):
def cmd_debug(self, *args):
self.buildozer.prepare_for_build()
self.build_mode = 'debug'
self.artifact_format = 'apk'
self.buildozer.build()

def cmd_release(self, *args):
Expand Down Expand Up @@ -137,6 +139,7 @@ def cmd_release(self, *args):
exit(1)

self.build_mode = 'release'
self.artifact_format = self.buildozer.config.getdefault('app', 'android.release_artifact', 'aab')
self.buildozer.build()

def cmd_deploy(self, *args):
Expand Down
127 changes: 63 additions & 64 deletions buildozer/targets/android.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
# does.
DEFAULT_SDK_TAG = '6514223'

DEFAULT_ARCH = 'armeabi-v7a'
DEFAULT_ARCHS = ['arm64-v8a', 'armeabi-v7a']

MSG_P4A_RECOMMENDED_NDK_ERROR = (
"WARNING: Unable to find recommended Android NDK for current "
Expand All @@ -62,21 +62,28 @@ class TargetAndroid(Target):
p4a_fork = 'kivy'
p4a_branch = 'master'
p4a_commit = 'HEAD'
p4a_apk_cmd = "apk --debug --bootstrap="
p4a_recommended_ndk_version = None
extra_p4a_args = ''

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._arch = self.buildozer.config.getdefault(
'app', 'android.arch', DEFAULT_ARCH)
if self.buildozer.config.has_option(
"app", "android.arch"
) and not self.buildozer.config.has_option("app", "android.archs"):
self.buildozer.error("`android.archs` not detected, instead `android.arch` is present.")
self.buildozer.error("`android.arch` will be removed and ignored in future.")
self.buildozer.error("If you're seeing this error, please migrate to `android.archs`.")
self._archs = self.buildozer.config.getlist(
'app', 'android.arch', DEFAULT_ARCHS)
else:
self._archs = self.buildozer.config.getlist(
'app', 'android.archs', DEFAULT_ARCHS)
self._build_dir = join(
self.buildozer.platform_dir, 'build-{}'.format(self._arch))
self.buildozer.platform_dir, 'build-{}'.format(self.archs_snake))
executable = sys.executable or 'python'
self._p4a_cmd = '{} -m pythonforandroid.toolchain '.format(executable)
self._p4a_bootstrap = self.buildozer.config.getdefault(
'app', 'p4a.bootstrap', 'sdl2')
self.p4a_apk_cmd += self._p4a_bootstrap
color = 'always' if USE_COLOR else 'never'
self.extra_p4a_args = ' --color={} --storage-dir="{}"'.format(
color, self._build_dir)
Expand Down Expand Up @@ -248,6 +255,10 @@ def sdkmanager_path(self):
'installed'.format(sdkmanager_path)))
return sdkmanager_path

@property
def archs_snake(self):
return "_".join(self._archs)

def check_requirements(self):
if platform in ('win32', 'cygwin'):
try:
Expand Down Expand Up @@ -315,6 +326,13 @@ def check_configuration_tokens(self):

super().check_configuration_tokens(errors)

def _p4a_have_aab_support(self):
returncode = self._p4a("aab -h", break_on_error=False, show_output=False)[2]
if returncode == 0:
return True
else:
return False

def _get_available_permissions(self):
key = 'android:available_permissions'
key_sdk = 'android:available_permissions_sdk'
Expand Down Expand Up @@ -687,6 +705,12 @@ def install_platform(self):
# ultimate configuration check.
# some of our configuration cannot be check without platform.
self.check_configuration_tokens()
if not self._p4a_have_aab_support():
self.buildozer.error(
"This buildozer version requires a python-for-android version with AAB (Android App Bundle) support. "
"Please update your pinned version accordingly."
)
raise BuildozerException()

self.buildozer.environ.update({
'PACKAGES_PATH': self.buildozer.global_packages_dir,
Expand Down Expand Up @@ -809,33 +833,29 @@ def compile_platform(self):
if local_recipes:
options.append('--local-recipes')
options.append(local_recipes)
self._p4a(
("create --dist_name={} --bootstrap={} --requirements={} "
"--arch {} {}").format(
dist_name, self._p4a_bootstrap, requirements,
self._arch, " ".join(options)),
get_stdout=True)[0]

p4a_create = "create --dist_name={} --bootstrap={} --requirements={} ".format(dist_name, self._p4a_bootstrap, requirements)

for arch in self._archs:
p4a_create += "--arch {} ".format(arch)

p4a_create += " ".join(options)

self._p4a(p4a_create, get_stdout=True)[0]

def get_available_packages(self):
return True

def get_dist_dir(self, dist_name, arch):
"""Find the dist dir with the given name and target arch, if one
def get_dist_dir(self, dist_name):
"""Find the dist dir with the given name if one
already exists, otherwise return a new dist_dir name.
"""
expected_dist_name = generate_dist_folder_name(dist_name, arch_names=[arch])

# If the expected dist name does exist, simply use that
expected_dist_dir = join(self._build_dir, 'dists', expected_dist_name)
expected_dist_dir = join(self._build_dir, 'dists', dist_name)
if exists(expected_dist_dir):
return expected_dist_dir

# For backwards compatibility, check if a directory without
# the arch exists. If so, this is probably the target dist.
old_dist_dir = join(self._build_dir, 'dists', dist_name)
if exists(old_dist_dir):
return old_dist_dir

# If no directory has been found yet, our dist probably
# doesn't exist yet, so use the expected name
return expected_dist_dir
Expand All @@ -848,7 +868,7 @@ def execute_build_package(self, build_cmd):
# wrapper from previous old_toolchain to new toolchain
dist_name = self.buildozer.config.get('app', 'package.name')
local_recipes = self.get_local_recipes_dir()
cmd = [self.p4a_apk_cmd, "--dist_name", dist_name]
cmd = [self.artifact_format, "--bootstrap", self._p4a_bootstrap, "--dist_name", dist_name]
for args in build_cmd:
option, values = args[0], args[1:]
if option == "debug":
Expand Down Expand Up @@ -951,14 +971,16 @@ def execute_build_package(self, build_cmd):
if compile_py:
cmd.append('--no-compile-pyo')

cmd.append('--arch')
cmd.append(self._arch)
for arch in self._archs:
cmd.append('--arch')
cmd.append(arch)

cmd = " ".join(cmd)
self._p4a(cmd)

def get_release_mode(self):
if self.check_p4a_sign_env():
# aab, also if unsigned is named as *-release
if self.check_p4a_sign_env() or self.artifact_format == "aab":
return "release"
return "release-unsigned"

Expand Down Expand Up @@ -1049,8 +1071,7 @@ def _generate_whitelist(self, dist_dir):

def build_package(self):
dist_name = self.buildozer.config.get('app', 'package.name')
arch = self.buildozer.config.getdefault('app', 'android.arch', DEFAULT_ARCH)
dist_dir = self.get_dist_dir(dist_name, arch)
dist_dir = self.get_dist_dir(dist_name)
config = self.buildozer.config
package = self._get_package()
version = self.buildozer.get_version()
Expand All @@ -1067,7 +1088,7 @@ def build_package(self):
patterns = config.getlist('app', config_key, [])
if not patterns:
continue
if self._arch != lib_dir:
if lib_dir not in self._archs:
continue

self.buildozer.debug('Search and copy libs for {}'.format(lib_dir))
Expand Down Expand Up @@ -1283,33 +1304,36 @@ def build_package(self):
if is_gradle_build:
# on gradle build, the apk use the package name, and have no version
packagename_src = basename(dist_dir) # gradle specifically uses the folder name
apk = u'{packagename}-{mode}.apk'.format(
packagename=packagename_src, mode=mode)
apk_dir = join(dist_dir, "build", "outputs", "apk", mode_sign)
artifact = u'{packagename}-{mode}.{artifact_format}'.format(
packagename=packagename_src, mode=mode, artifact_format=self.artifact_format)
if self.artifact_format == "apk":
artifact_dir = join(dist_dir, "build", "outputs", "apk", mode_sign)
elif self.artifact_format == "aab":
artifact_dir = join(dist_dir, "build", "outputs", "bundle", mode_sign)
else:
# on ant, the apk use the title, and have version
bl = u'\'" ,'
apptitle = config.get('app', 'title')
if hasattr(apptitle, 'decode'):
apptitle = apptitle.decode('utf-8')
apktitle = ''.join([x for x in apptitle if x not in bl])
apk = u'{title}-{version}-{mode}.apk'.format(
artifact = u'{title}-{version}-{mode}.apk'.format(
title=apktitle,
version=version,
mode=mode)
apk_dir = join(dist_dir, "bin")
artifact_dir = join(dist_dir, "bin")

apk_dest = u'{packagename}-{version}-{arch}-{mode}.apk'.format(
artifact_dest = u'{packagename}-{version}-{arch}-{mode}.{artifact_format}'.format(
packagename=packagename, mode=mode, version=version,
arch=self._arch)
arch=self.archs_snake, artifact_format=self.artifact_format)

# copy to our place
copyfile(join(apk_dir, apk), join(self.buildozer.bin_dir, apk_dest))
copyfile(join(artifact_dir, artifact), join(self.buildozer.bin_dir, artifact_dest))

self.buildozer.info('Android packaging done!')
self.buildozer.info(
u'APK {0} available in the bin directory'.format(apk_dest))
self.buildozer.state['android:latestapk'] = apk_dest
u'APK {0} available in the bin directory'.format(artifact_dest))
self.buildozer.state['android:latestapk'] = artifact_dest
self.buildozer.state['android:latestmode'] = self.build_mode

def _update_libraries_references(self, dist_dir):
Expand Down Expand Up @@ -1492,28 +1516,3 @@ def cmd_logcat(self, *args):
def get_target(buildozer):
buildozer.targetname = "android"
return TargetAndroid(buildozer)


def generate_dist_folder_name(base_dist_name, arch_names=None):
"""Generate the distribution folder name to use, based on a
combination of the input arguments.
WARNING: This function is copied from python-for-android. It would
be preferable to have a proper interface, either importing the p4a
code or having a p4a dist dir query option.
Parameters
----------
base_dist_name : str
The core distribution identifier string
arch_names : list of str
The architecture compile targets
"""
if arch_names is None:
arch_names = ["no_arch_specified"]

return '{}__{}'.format(
base_dist_name,
'_'.join(arch_names)
)
2 changes: 1 addition & 1 deletion docs/source/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Init and build for Android
Don't worry, thoses files will be saved in a global directory and will be
shared across the different project you'll manage with Buildozer.

#. At the end, you should have an APK file in the `bin/` directory.
#. At the end, you should have an APK or AAB file in the `bin/` directory.


Run my application
Expand Down
11 changes: 5 additions & 6 deletions tests/targets/test_android.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def call_build_package(target_android):
"""
buildozer = target_android.buildozer
expected_dist_dir = (
'{buildozer_dir}/android/platform/build-armeabi-v7a/dists/myapp__armeabi-v7a'.format(
'{buildozer_dir}/android/platform/build-arm64-v8a_armeabi-v7a/dists/myapp'.format(
buildozer_dir=buildozer.buildozer_dir)
)

Expand All @@ -79,7 +79,7 @@ def call_build_package(target_android):
'{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=buildozer.bin_dir),
'{bin_dir}/myapp-0.1-arm64-v8a_armeabi-v7a-debug.apk'.format(bin_dir=buildozer.bin_dir),
)
]
return m_execute_build_package
Expand All @@ -104,9 +104,9 @@ def test_init(self):
"""Tests init defaults."""
target_android = init_target(self.temp_dir)
buildozer = target_android.buildozer
assert target_android._arch == "armeabi-v7a"
assert target_android._archs == ["arm64-v8a", "armeabi-v7a"]
assert target_android._build_dir.endswith(
".buildozer/android/platform/build-armeabi-v7a"
".buildozer/android/platform/build-arm64-v8a_armeabi-v7a"
)
assert target_android._p4a_bootstrap == "sdl2"
assert target_android._p4a_cmd.endswith(
Expand All @@ -116,11 +116,10 @@ def test_init(self):
assert (
target_android.extra_p4a_args == (
' --color=always'
' --storage-dir="{buildozer_dir}/android/platform/build-armeabi-v7a" --ndk-api=21 --ignore-setup-py --debug'.format(
' --storage-dir="{buildozer_dir}/android/platform/build-arm64-v8a_armeabi-v7a" --ndk-api=21 --ignore-setup-py --debug'.format(
buildozer_dir=buildozer.buildozer_dir)
)
)
assert target_android.p4a_apk_cmd == "apk --debug --bootstrap=sdl2"
assert target_android.platform_update is False

def test_init_positional_buildozer(self):
Expand Down

0 comments on commit 98a9b27

Please sign in to comment.