introduce new android_new target using the latest python-for-android toolchain (and not the old_toolchain) version. It's not fully customizable, but support all the basic options plus a couple of new one (copy_libs, service)

+ improve logcat command
This commit is contained in:
Mathieu Virbel 2016-01-24 13:58:21 +01:00
parent 77c5e7c08e
commit 7091cf098c
3 changed files with 235 additions and 114 deletions

View file

@ -51,6 +51,9 @@ requirements = kivy
# (str) Supported orientation (one of landscape, portrait or all)
orientation = landscape
# (list) List of service to declare
#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY
#
# OSX Specific
#
@ -142,6 +145,12 @@ fullscreen = 1
# project.properties automatically.)
#android.library_references =
# (str) Android logcat filters to use
#android.logcat_filters = *:S python:D
# (bool) Copy library instead of making a libpymodules.so
#android.copy_libs = 1
#
# iOS specific
#

View file

@ -34,25 +34,29 @@ from buildozer.libs.version import parse
class TargetAndroid(Target):
p4a_branch = "old_toolchain"
p4a_directory = "python-for-android"
p4a_apk_cmd = "{python} build.py"
@property
def android_sdk_version(self):
return self.buildozer.config.getdefault(
'app', 'android.sdk', ANDROID_SDK_VERSION)
return self.buildozer.config.getdefault('app', 'android.sdk',
ANDROID_SDK_VERSION)
@property
def android_ndk_version(self):
return self.buildozer.config.getdefault(
'app', 'android.ndk', ANDROID_NDK_VERSION)
return self.buildozer.config.getdefault('app', 'android.ndk',
ANDROID_NDK_VERSION)
@property
def android_api(self):
return self.buildozer.config.getdefault(
'app', 'android.api', ANDROID_API)
return self.buildozer.config.getdefault('app', 'android.api',
ANDROID_API)
@property
def android_minapi(self):
return self.buildozer.config.getdefault(
'app', 'android.minapi', ANDROID_MINAPI)
return self.buildozer.config.getdefault('app', 'android.minapi',
ANDROID_MINAPI)
@property
def android_sdk_dir(self):
@ -60,8 +64,8 @@ class TargetAndroid(Target):
'app', 'android.sdk_path', ''))
if directory:
return realpath(directory)
version = self.buildozer.config.getdefault(
'app', 'android.sdk', self.android_sdk_version)
version = self.buildozer.config.getdefault('app', 'android.sdk',
self.android_sdk_version)
return join(self.buildozer.global_platform_dir,
'android-sdk-{0}'.format(version))
@ -71,8 +75,8 @@ class TargetAndroid(Target):
'app', 'android.ndk_path', ''))
if directory:
return realpath(directory)
version = self.buildozer.config.getdefault(
'app', 'android.ndk', self.android_ndk_version)
version = self.buildozer.config.getdefault('app', 'android.ndk',
self.android_ndk_version)
return join(self.buildozer.global_platform_dir,
'android-ndk-r{0}'.format(version))
@ -82,8 +86,8 @@ class TargetAndroid(Target):
'app', 'android.ant_path', ''))
if directory:
return realpath(directory)
version = self.buildozer.config.getdefault(
'app', 'android.ant', APACHE_ANT_VERSION)
version = self.buildozer.config.getdefault('app', 'android.ant',
APACHE_ANT_VERSION)
return join(self.buildozer.global_platform_dir,
'apache-ant-{0}'.format(version))
@ -111,8 +115,7 @@ class TargetAndroid(Target):
self.keytool_cmd = self._locate_java('keytool')
# Check for C header <zlib.h>.
_, _, returncode_dpkg = self.buildozer.cmd(
'dpkg --version',
_, _, returncode_dpkg = self.buildozer.cmd('dpkg --version',
break_on_error=False)
is_debian_like = (returncode_dpkg == 0)
if is_debian_like and \
@ -195,12 +198,11 @@ class TargetAndroid(Target):
_winreg.HKEY_LOCAL_MACHINE,
r"SOFTWARE\JavaSoft\Java Development Kit") as jdk: #@UndefinedVariable
current_version, _type = _winreg.QueryValueEx(
jdk, "CurrentVersion"
) #@UndefinedVariable
jdk, "CurrentVersion") #@UndefinedVariable
with _winreg.OpenKey(jdk,
current_version) as cv: #@UndefinedVariable
java_home, _type = _winreg.QueryValueEx(cv, "JavaHome"
) #@UndefinedVariable
java_home, _type = _winreg.QueryValueEx(
cv, "JavaHome") #@UndefinedVariable
self.buildozer.environ['JAVA_HOME'] = java_home
def _locate_java(self, s):
@ -221,7 +223,8 @@ class TargetAndroid(Target):
self.buildozer.info('Android ANT is missing, downloading')
archive = 'apache-ant-{0}-bin.tar.gz'.format(APACHE_ANT_VERSION)
url = 'http://archive.apache.org/dist/ant/binaries/'
self.buildozer.download(url, archive,
self.buildozer.download(url,
archive,
cwd=self.buildozer.global_platform_dir)
self.buildozer.file_extract(archive,
cwd=self.buildozer.global_platform_dir)
@ -249,13 +252,15 @@ class TargetAndroid(Target):
archive = archive.format(self.android_sdk_version)
url = 'http://dl.google.com/android/'
self.buildozer.download(url, archive,
self.buildozer.download(url,
archive,
cwd=self.buildozer.global_platform_dir)
self.buildozer.info('Unpacking Android SDK')
self.buildozer.file_extract(archive,
cwd=self.buildozer.global_platform_dir)
self.buildozer.file_rename(unpacked, sdk_dir,
self.buildozer.file_rename(unpacked,
sdk_dir,
cwd=self.buildozer.global_platform_dir)
self.buildozer.info('Android SDK installation done.')
@ -298,13 +303,15 @@ class TargetAndroid(Target):
archive = archive.format(self.android_ndk_version, architecture)
unpacked = unpacked.format(self.android_ndk_version)
url = 'http://dl.google.com/android/ndk/'
self.buildozer.download(url, archive,
self.buildozer.download(url,
archive,
cwd=self.buildozer.global_platform_dir)
self.buildozer.info('Unpacking Android NDK')
self.buildozer.file_extract(archive,
cwd=self.buildozer.global_platform_dir)
self.buildozer.file_rename(unpacked, ndk_dir,
self.buildozer.file_rename(unpacked,
ndk_dir,
cwd=self.buildozer.global_platform_dir)
self.buildozer.info('Android NDK installation done.')
return ndk_dir
@ -321,7 +328,8 @@ class TargetAndroid(Target):
# get only the line like -> id: 5 or "build-tools-19.0.1"
# and extract the name part.
print(available_packages)
return [x.split('"')[1] for x in available_packages.splitlines()
return [x.split('"')[1]
for x in available_packages.splitlines()
if x.startswith('id: ')]
def _android_update_sdk(self, packages):
@ -329,7 +337,8 @@ class TargetAndroid(Target):
java_tool_options = environ.get('JAVA_TOOL_OPTIONS', '')
child = self.buildozer.cmd_expect(
'{} update sdk -u -a -t {}'.format(
self.android_cmd, packages,
self.android_cmd,
packages,
cwd=self.buildozer.global_platform_dir),
timeout=None,
env={
@ -348,8 +357,8 @@ class TargetAndroid(Target):
def _read_version_subdir(self, *args):
versions = []
if not os.path.exists(join(*args)):
self.buildozer.debug(
'build-tools folder not found {}'.format(join(*args)))
self.buildozer.debug('build-tools folder not found {}'.format(join(
*args)))
return parse("0")
for v in os.listdir(join(*args)):
try:
@ -396,8 +405,8 @@ class TargetAndroid(Target):
self._android_update_sdk('tools,platform-tools')
# 2. install the latest build tool
v_build_tools = self._read_version_subdir(
self.android_sdk_dir, 'build-tools')
v_build_tools = self._read_version_subdir(self.android_sdk_dir,
'build-tools')
packages = self._android_list_sdk(include_all=True)
ver = self._find_latest_package(packages, 'build-tools-')
if ver and ver > v_build_tools:
@ -422,8 +431,8 @@ class TargetAndroid(Target):
def _check_aidl(self, v_build_tools):
self.buildozer.debug('Check that aidl can be executed')
v_build_tools = self._read_version_subdir(
self.android_sdk_dir, 'build-tools')
v_build_tools = self._read_version_subdir(self.android_sdk_dir,
'build-tools')
aidl_cmd = join(self.android_sdk_dir, 'build-tools',
str(v_build_tools), 'aidl')
self.buildozer.checkbin('Aidl', aidl_cmd)
@ -449,20 +458,24 @@ class TargetAndroid(Target):
def install_platform(self):
cmd = self.buildozer.cmd
self.pa_dir = pa_dir = join(self.buildozer.platform_dir,
'python-for-android')
if not self.buildozer.file_exists(pa_dir):
system_p4a_dir = self.buildozer.config.getdefault(
'app', 'android.p4a_dir')
self.p4a_directory)
system_p4a_dir = self.buildozer.config.getdefault('app',
'android.p4a_dir')
if system_p4a_dir:
cmd('ln -sf {} ./python-for-android'.format(system_p4a_dir),
cwd=self.buildozer.platform_dir)
self.pa_dir = pa_dir = system_p4a_dir
if not self.buildozer.file_exists(pa_dir):
self.buildozer.critical(
'Path for android.p4a_dir doesnt exists')
else:
cmd('git clone -b old_toolchain --single-branch '
'https://github.com/kivy/python-for-android.git',
if not self.buildozer.file_exists(pa_dir):
cmd(
('git clone -b {} --single-branch '
'https://github.com/kivy/python-for-android.git '
'{}').format(self.p4a_branch, self.p4a_directory),
cwd=self.buildozer.platform_dir)
elif self.platform_update:
cmd('git clean -dxf', cwd=pa_dir)
cmd('git pull origin master', cwd=pa_dir)
cmd('git pull origin {}'.format(self.p4a_branch), cwd=pa_dir)
source = self.buildozer.config.getdefault('app', 'android.branch')
if source:
@ -486,8 +499,7 @@ class TargetAndroid(Target):
})
def get_available_packages(self):
available_modules = self.buildozer.cmd(
'./distribute.sh -l',
available_modules = self.buildozer.cmd('./distribute.sh -l',
cwd=self.pa_dir,
get_stdout=True)[0]
if not available_modules.startswith('Available modules:'):
@ -506,7 +518,8 @@ class TargetAndroid(Target):
# about
available_modules = self.get_available_packages()
onlyname = lambda x: x.split('==')[0]
android_requirements = [x for x in app_requirements
android_requirements = [x
for x in app_requirements
if onlyname(x) in available_modules]
need_compile = 0
@ -575,9 +588,20 @@ class TargetAndroid(Target):
for wl in p4a_whitelist:
fd.write(wl + '\n')
def get_dist_dir(self, dist_name):
return join(self.pa_dir, 'dist', dist_name)
def execute_build_package(self, build_cmd):
dist_name = self.buildozer.config.get('app', 'package.name')
cmd = [self.p4a_apk_cmd]
for args in build_cmd:
cmd.append(" ".join(args))
cmd = " ".join(cmd)
self.buildozer.cmd(cmd, cwd=self.get_dist_dir(dist_name))
def build_package(self):
dist_name = self.buildozer.config.get('app', 'package.name')
dist_dir = join(self.pa_dir, 'dist', dist_name)
dist_dir = self.get_dist_dir(dist_name)
config = self.buildozer.config
package = self._get_package()
version = self.buildozer.get_version()
@ -587,8 +611,8 @@ class TargetAndroid(Target):
for config_key, lib_dir in (
('android.add_libs_armeabi', 'armeabi'),
('android.add_libs_armeabi_v7a', 'armeabi-v7a'),
('android.add_libs_x86', 'x86'), ('android.add_libs_mips', 'mips')
):
('android.add_libs_x86', 'x86'),
('android.add_libs_mips', 'mips')):
patterns = config.getlist('app', config_key, [])
if not patterns:
@ -610,24 +634,21 @@ class TargetAndroid(Target):
self._generate_whitelist(dist_dir)
# build the app
build_cmd = (
'{python} build.py --name {name}'
' --version {version}'
' --package {package}'
' --{storage_type} "{appdir}"'
' --sdk {androidsdk}'
' --minsdk {androidminsdk}').format(
python=executable,
name=quote(config.get('app', 'title')),
version=version,
package=package,
storage_type='private' if config.getbooldefault(
'app', 'android.private_storage', True) else 'dir',
appdir=self.buildozer.app_dir,
androidminsdk=config.getdefault(
'app', 'android.minsdk', self.android_minapi),
androidsdk=config.getdefault(
'app', 'android.sdk', self.android_api))
build_cmd = [
("--name", quote(config.get('app', 'title'))),
("--version", version),
("--package", package),
("--sdk", config.getdefault('app', 'android.sdk',
self.android_api)),
("--minsdk", config.getdefault('app', 'android.minsdk',
self.android_minapi)),
]
is_private_storage = config.getbooldefault(
'app', 'android.private_storage', True)
if is_private_storage:
build_cmd += [("--private", self.buildozer.app_dir)]
else:
build_cmd += [("--dir", self.buildozer.app_dir)]
# add permissions
permissions = config.getlist('app', 'android.permissions', [])
@ -636,14 +657,14 @@ class TargetAndroid(Target):
permission = permission.split('.')
permission[-1] = permission[-1].upper()
permission = '.'.join(permission)
build_cmd += ' --permission {}'.format(permission)
build_cmd += [("--permission", permission)]
# meta-data
meta_datas = config.getlistvalues('app', 'android.meta_data', [])
for meta in meta_datas:
key, value = meta.split('=', 1)
meta = '{}={}'.format(key.strip(), value.strip())
build_cmd += ' --meta-data "{}"'.format(meta)
build_cmd += [("--meta-data", meta)]
# add extra Java jar files
add_jars = config.getlist('app', 'android.add_jars', [])
@ -652,22 +673,21 @@ class TargetAndroid(Target):
matches = glob(expanduser(pattern.strip()))
if matches:
for jar in matches:
build_cmd += ' --add-jar "{}"'.format(jar)
build_cmd += [("--add-jar", jar)]
else:
raise SystemError(
'Failed to find jar file: {}'.format(pattern))
raise SystemError('Failed to find jar file: {}'.format(
pattern))
# add presplash
presplash = config.getdefault('app', 'presplash.filename', '')
if presplash:
build_cmd += ' --presplash {}'.format(join(self.buildozer.root_dir,
presplash))
build_cmd += [("--presplash", join(self.buildozer.root_dir,
presplash))]
# add icon
icon = config.getdefault('app', 'icon.filename', '')
if icon:
build_cmd += ' --icon {}'.format(join(self.buildozer.root_dir,
icon))
build_cmd += [("--icon", join(self.buildozer.root_dir, icon))]
# OUYA Console support
ouya_category = config.getdefault('app', 'android.ouya.category',
@ -678,51 +698,50 @@ class TargetAndroid(Target):
'Invalid android.ouya.category: "{}" must be one of GAME or APP'.format(
ouya_category))
# add icon
build_cmd += ' --ouya-category {}'.format(ouya_category)
ouya_icon = config.getdefault('app', 'android.ouya.icon.filename',
'')
build_cmd += ' --ouya-icon {}'.format(join(self.buildozer.root_dir,
ouya_icon))
build_cmd += [("--ouya-category", ouya_category)]
build_cmd += [("--ouya-icon", join(self.buildozer.root_dir,
ouya_icon))]
# add orientation
orientation = config.getdefault('app', 'orientation', 'landscape')
if orientation == 'all':
orientation = 'sensor'
build_cmd += ' --orientation {}'.format(orientation)
build_cmd += [("--orientation", orientation)]
# fullscreen ?
fullscreen = config.getbooldefault('app', 'fullscreen', True)
if not fullscreen:
build_cmd += ' --window'
build_cmd += [("--window", )]
# wakelock ?
wakelock = config.getbooldefault('app', 'android.wakelock', False)
if wakelock:
build_cmd += ' --wakelock'
build_cmd += [("--wakelock", )]
# intent filters
intent_filters = config.getdefault('app',
'android.manifest.intent_filters',
'')
intent_filters = config.getdefault(
'app', 'android.manifest.intent_filters', '')
if intent_filters:
build_cmd += ' --intent-filters {}'.format(
join(self.buildozer.root_dir, intent_filters))
build_cmd += [("--intent-filters", join(self.buildozer.root_dir,
intent_filters))]
# build only in debug right now.
if self.build_mode == 'debug':
build_cmd += ' debug'
build_cmd += [("debug", )]
mode = 'debug'
else:
build_cmd += ' release'
build_cmd += [("release", )]
mode = 'release-unsigned'
self.buildozer.cmd(build_cmd, cwd=dist_dir)
self.execute_build_package(build_cmd)
# XXX found how the apk name is really built from the title
bl = '\'" ,'
apktitle = ''.join([x for x in config.get('app', 'title')
if x not in bl])
apk = '{title}-{version}-{mode}.apk'.format(
title=apktitle,
apktitle = ''.join([x for x in config.get('app', 'title') if x not in
bl])
apk = '{title}-{version}-{mode}.apk'.format(title=apktitle,
version=version,
mode=mode)
@ -730,8 +749,8 @@ class TargetAndroid(Target):
copyfile(join(dist_dir, 'bin', apk), join(self.buildozer.bin_dir, apk))
self.buildozer.info('Android packaging done!')
self.buildozer.info(
'APK {0} available in the bin directory'.format(apk))
self.buildozer.info('APK {0} available in the bin directory'.format(
apk))
self.buildozer.state['android:latestapk'] = apk
self.buildozer.state['android:latestmode'] = self.build_mode
@ -755,8 +774,8 @@ class TargetAndroid(Target):
# convert our references to relative path
app_references = self.buildozer.config.getlist(
'app', 'android.library_references', [])
source_dir = realpath(
self.buildozer.config.getdefault('app', 'source.dir', '.'))
source_dir = realpath(self.buildozer.config.getdefault(
'app', 'source.dir', '.'))
for cref in app_references:
# get the full path of the current reference
ref = realpath(join(source_dir, cref))
@ -778,8 +797,8 @@ class TargetAndroid(Target):
else:
fd.write(line.decode('utf-8'))
for index, ref in enumerate(references):
fd.write(u'android.library.reference.{}={}\n'.format(
index + 1, ref))
fd.write(u'android.library.reference.{}={}\n'.format(index + 1,
ref))
self.buildozer.debug('project.properties updated')
@ -830,8 +849,8 @@ class TargetAndroid(Target):
for serial in self.serials:
self.buildozer.environ['ANDROID_SERIAL'] = serial
self.buildozer.info('Deploy on {}'.format(serial))
self.buildozer.cmd('{0} install -r {1}'.format(
self.adb_cmd, full_apk),
self.buildozer.cmd('{0} install -r {1}'.format(self.adb_cmd,
full_apk),
cwd=self.buildozer.global_platform_dir)
self.buildozer.environ.pop('ANDROID_SERIAL', None)
@ -865,8 +884,11 @@ class TargetAndroid(Target):
serial = self.serials[0:]
if not serial:
return
filters = self.buildozer.config.getdefault(
"app", "android.logcat_filters", "")
self.buildozer.environ['ANDROID_SERIAL'] = serial[0]
self.buildozer.cmd('{adb} logcat'.format(adb=self.adb_cmd),
self.buildozer.cmd('{adb} logcat {filters}'.format(adb=self.adb_cmd,
filters=filters),
cwd=self.buildozer.global_platform_dir,
show_output=True)
self.buildozer.environ.pop('ANDROID_SERIAL', None)

View file

@ -0,0 +1,90 @@
# coding=utf-8
from buildozer.targets.android import TargetAndroid
from os.path import join, expanduser
class TargetAndroidNew(TargetAndroid):
p4a_branch = "master"
p4a_directory = "python-for-android-master"
p4a_apk_cmd = "python -m pythonforandroid.toolchain apk --bootstrap=sdl2"
def get_available_packages(self):
available_modules = self.buildozer.cmd(
"python -m pythonforandroid.toolchain recipes --compact",
cwd=self.pa_dir,
get_stdout=True)[0]
return available_modules.splitlines()[0].split()
def compile_platform(self):
app_requirements = self.buildozer.config.getlist(
'app', 'requirements', '')
available_modules = self.get_available_packages()
onlyname = lambda x: x.split('==')[0]
android_requirements = [x for x in app_requirements
if onlyname(x) in available_modules]
dist_name = self.buildozer.config.get('app', 'package.name')
requirements = ','.join(android_requirements)
options = []
if self.buildozer.config.getbooldefault('app', 'android.copy_libs', True):
options.append("--copy-libs")
available_modules = self.buildozer.cmd(
("python -m pythonforandroid.toolchain "
"create --dist_name={} --bootstrap={} --requirements={} --arch armeabi-v7a {}").format(
dist_name, "sdl2", requirements, " ".join(options)),
cwd=self.pa_dir,
get_stdout=True)[0]
def _update_libraries_references(self, dist_dir):
# UNSUPPORTED YET
pass
def get_dist_dir(self, dist_name):
return expanduser(join("~", ".local", "share", "python-for-android",
'dists', dist_name))
def execute_build_package(self, build_cmd):
# wrapper from previous old_toolchain to new toolchain
dist_name = self.buildozer.config.get('app', 'package.name')
cmd = [self.p4a_apk_cmd, "--dist_name", dist_name]
print cmd, build_cmd
for args in build_cmd:
option, values = args[0], args[1:]
if option == "debug":
continue
elif option == "release":
cmd.append("--release")
continue
if option in ("--window", ):
# missing option in sdl2 bootstrap yet
continue
elif option == "--sdk":
cmd.append("--android_api")
cmd.extend(values)
else:
cmd.extend(args)
# support for services
services = self.buildozer.config.getlist('app', 'services', [])
for service in services:
cmd.append("--service")
cmd.append(service)
# support for copy-libs
if self.buildozer.config.getbooldefault('app', 'android.copy_libs', True):
cmd.append("--copy-libs")
cmd = " ".join(cmd)
self.buildozer.cmd(cmd, cwd=self.pa_dir)
def cmd_run(self, *args):
entrypoint = self.buildozer.config.getdefault(
'app', 'android.entrypoint')
if not entrypoint:
self.buildozer.config.set('app', 'android.entrypoint', 'org.kivy.android.PythonActivity')
return super(TargetAndroidNew, self).cmd_run(*args)
def get_target(buildozer):
buildozer.targetname = "android"
return TargetAndroidNew(buildozer)