This commit is contained in:
Mathieu Virbel 2015-10-04 11:52:20 +02:00
parent ecc5698893
commit c7fca65d04

View file

@ -34,26 +34,25 @@ from buildozer.libs.version import parse
class TargetAndroid(Target):
@property
def android_sdk_version(self):
return self.buildozer.config.getdefault(
'app', 'android.sdk', ANDROID_SDK_VERSION)
'app', 'android.sdk', ANDROID_SDK_VERSION)
@property
def android_ndk_version(self):
return self.buildozer.config.getdefault(
'app', 'android.ndk', ANDROID_NDK_VERSION)
'app', 'android.ndk', ANDROID_NDK_VERSION)
@property
def android_api(self):
return self.buildozer.config.getdefault(
'app', 'android.api', ANDROID_API)
'app', 'android.api', ANDROID_API)
@property
def android_minapi(self):
return self.buildozer.config.getdefault(
'app', 'android.minapi', ANDROID_MINAPI)
'app', 'android.minapi', ANDROID_MINAPI)
@property
def android_sdk_dir(self):
@ -62,9 +61,9 @@ class TargetAndroid(Target):
if directory:
return realpath(directory)
version = self.buildozer.config.getdefault(
'app', 'android.sdk', self.android_sdk_version)
'app', 'android.sdk', self.android_sdk_version)
return join(self.buildozer.global_platform_dir,
'android-sdk-{0}'.format(version))
'android-sdk-{0}'.format(version))
@property
def android_ndk_dir(self):
@ -73,9 +72,9 @@ class TargetAndroid(Target):
if directory:
return realpath(directory)
version = self.buildozer.config.getdefault(
'app', 'android.ndk', self.android_ndk_version)
'app', 'android.ndk', self.android_ndk_version)
return join(self.buildozer.global_platform_dir,
'android-ndk-r{0}'.format(version))
'android-ndk-r{0}'.format(version))
@property
def apache_ant_dir(self):
@ -84,9 +83,9 @@ class TargetAndroid(Target):
if directory:
return realpath(directory)
version = self.buildozer.config.getdefault(
'app', 'android.ant', APACHE_ANT_VERSION)
'app', 'android.ant', APACHE_ANT_VERSION)
return join(self.buildozer.global_platform_dir,
'apache-ant-{0}'.format(version))
'apache-ant-{0}'.format(version))
def check_requirements(self):
if platform in ('win32', 'cygwin'):
@ -94,8 +93,10 @@ class TargetAndroid(Target):
self._set_win32_java_home()
except:
traceback.print_exc()
self.android_cmd = join(self.android_sdk_dir, 'tools', 'android.bat')
self.adb_cmd = join(self.android_sdk_dir, 'platform-tools', 'adb.exe')
self.android_cmd = join(self.android_sdk_dir, 'tools',
'android.bat')
self.adb_cmd = join(self.android_sdk_dir, 'platform-tools',
'adb.exe')
self.javac_cmd = self._locate_java('javac.exe')
self.keytool_cmd = self._locate_java('keytool.exe')
elif platform in ('darwin', ):
@ -111,13 +112,14 @@ class TargetAndroid(Target):
# Check for C header <zlib.h>.
_, _, returncode_dpkg = self.buildozer.cmd(
'dpkg --version', break_on_error= False)
'dpkg --version',
break_on_error=False)
is_debian_like = (returncode_dpkg == 0)
if is_debian_like and \
not self.buildozer.file_exists('/usr/include/zlib.h'):
raise BuildozerException(
'zlib headers must be installed, '
'run: sudo apt-get install zlib1g-dev')
raise BuildozerException(
'zlib headers must be installed, '
'run: sudo apt-get install zlib1g-dev')
# Need to add internally installed ant to path for external tools
# like adb to use
@ -168,13 +170,15 @@ class TargetAndroid(Target):
return self.buildozer.state[key]
try:
self.buildozer.debug('Read available permissions from api-versions.xml')
self.buildozer.debug(
'Read available permissions from api-versions.xml')
import xml.etree.ElementTree as ET
fn = join(self.android_sdk_dir, 'platform-tools',
'api', 'api-versions.xml')
fn = join(self.android_sdk_dir, 'platform-tools', 'api',
'api-versions.xml')
with io.open(fn, encoding='utf-8') as fd:
doc = ET.fromstring(fd.read())
fields = doc.findall('.//class[@name="android/Manifest$permission"]/field[@name]')
fields = doc.findall(
'.//class[@name="android/Manifest$permission"]/field[@name]')
available_permissions = [x.attrib['name'] for x in fields]
self.buildozer.state[key] = available_permissions
@ -187,10 +191,15 @@ class TargetAndroid(Target):
if 'JAVA_HOME' in self.buildozer.environ:
return
import _winreg
with _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\JavaSoft\Java Development Kit") as jdk: #@UndefinedVariable
current_version, _type = _winreg.QueryValueEx(jdk, "CurrentVersion") #@UndefinedVariable
with _winreg.OpenKey(jdk, current_version) as cv: #@UndefinedVariable
java_home, _type = _winreg.QueryValueEx(cv, "JavaHome") #@UndefinedVariable
with _winreg.OpenKey(
_winreg.HKEY_LOCAL_MACHINE,
r"SOFTWARE\JavaSoft\Java Development Kit") as jdk: #@UndefinedVariable
current_version, _type = _winreg.QueryValueEx(
jdk, "CurrentVersion") #@UndefinedVariable
with _winreg.OpenKey(jdk,
current_version) as cv: #@UndefinedVariable
java_home, _type = _winreg.QueryValueEx(cv, "JavaHome"
) #@UndefinedVariable
self.buildozer.environ['JAVA_HOME'] = java_home
def _locate_java(self, s):
@ -212,9 +221,9 @@ class TargetAndroid(Target):
archive = 'apache-ant-{0}-bin.tar.gz'.format(APACHE_ANT_VERSION)
url = 'http://archive.apache.org/dist/ant/binaries/'
self.buildozer.download(url, archive,
cwd=self.buildozer.global_platform_dir)
cwd=self.buildozer.global_platform_dir)
self.buildozer.file_extract(archive,
cwd=self.buildozer.global_platform_dir)
cwd=self.buildozer.global_platform_dir)
self.buildozer.info('Apache ANT installation done.')
return ant_dir
@ -240,13 +249,13 @@ class TargetAndroid(Target):
archive = archive.format(self.android_sdk_version)
url = 'http://dl.google.com/android/'
self.buildozer.download(url, archive,
cwd=self.buildozer.global_platform_dir)
cwd=self.buildozer.global_platform_dir)
self.buildozer.info('Unpacking Android SDK')
self.buildozer.file_extract(archive,
cwd=self.buildozer.global_platform_dir)
cwd=self.buildozer.global_platform_dir)
self.buildozer.file_rename(unpacked, sdk_dir,
cwd=self.buildozer.global_platform_dir)
cwd=self.buildozer.global_platform_dir)
self.buildozer.info('Android SDK installation done.')
return sdk_dir
@ -264,7 +273,7 @@ class TargetAndroid(Target):
# Checking of 32/64 bits at Windows from: http://stackoverflow.com/a/1405971/798575
import struct
archive = 'android-ndk-r{0}-windows-{1}.zip'
is_64 = (8*struct.calcsize("P") == 64)
is_64 = (8 * struct.calcsize("P") == 64)
elif platform in ('darwin', ):
if int(_version) > 9:
@ -274,7 +283,7 @@ class TargetAndroid(Target):
is_64 = (os.uname()[4] == 'x86_64')
elif platform.startswith('linux'):
if int(_version) > 9: # if greater than 9, take it as .bin file
if int(_version) > 9: # if greater than 9, take it as .bin file
archive = 'android-ndk-r{0}-linux-{1}.bin'
else:
archive = 'android-ndk-r{0}-linux-{1}.tar.bz2'
@ -288,13 +297,13 @@ class TargetAndroid(Target):
unpacked = unpacked.format(self.android_ndk_version)
url = 'http://dl.google.com/android/ndk/'
self.buildozer.download(url, archive,
cwd=self.buildozer.global_platform_dir)
cwd=self.buildozer.global_platform_dir)
self.buildozer.info('Unpacking Android NDK')
self.buildozer.file_extract(archive,
cwd=self.buildozer.global_platform_dir)
cwd=self.buildozer.global_platform_dir)
self.buildozer.file_rename(unpacked, ndk_dir,
cwd=self.buildozer.global_platform_dir)
cwd=self.buildozer.global_platform_dir)
self.buildozer.info('Android NDK installation done.')
return ndk_dir
@ -303,24 +312,28 @@ class TargetAndroid(Target):
if include_all:
cmd += ' -a'
available_packages = self.buildozer.cmd(
cmd,
cwd=self.buildozer.global_platform_dir,
get_stdout=True)[0]
cmd,
cwd=self.buildozer.global_platform_dir,
get_stdout=True)[0]
# 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() if x.startswith('id: ')]
return [x.split('"')[1] for x in available_packages.splitlines()
if x.startswith('id: ')]
def _android_update_sdk(self, packages):
from pexpect import EOF
java_tool_options = environ.get('JAVA_TOOL_OPTIONS', '')
child = self.buildozer.cmd_expect('{} update sdk -u -a -t {}'.format(
self.android_cmd, packages,
cwd=self.buildozer.global_platform_dir),
child = self.buildozer.cmd_expect(
'{} update sdk -u -a -t {}'.format(
self.android_cmd, packages,
cwd=self.buildozer.global_platform_dir),
timeout=None,
env={'JAVA_TOOL_OPTIONS': java_tool_options + ' -Dfile.encoding=UTF-8'})
env={
'JAVA_TOOL_OPTIONS': java_tool_options +
' -Dfile.encoding=UTF-8'
})
while True:
index = child.expect([EOF, u'[y/n]: '])
if index == 0:
@ -365,11 +378,9 @@ class TargetAndroid(Target):
# update
cache_key = 'android:sdk_installation'
cache_value = [
self.android_api,
self.android_minapi,
self.android_ndk_version,
self.android_sdk_dir,
self.android_ndk_dir]
self.android_api, self.android_minapi, self.android_ndk_version,
self.android_sdk_dir, self.android_ndk_dir
]
if self.buildozer.state.get(cache_key, None) == cache_value:
return True
@ -384,15 +395,16 @@ class TargetAndroid(Target):
# 2. install the latest build tool
v_build_tools = self._read_version_subdir(
self.android_sdk_dir, 'build-tools')
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:
self._android_update_sdk(self._build_package_string('build-tools', ver))
self._android_update_sdk(self._build_package_string('build-tools',
ver))
# 3. finally, install the android for the current api
android_platform = join(self.android_sdk_dir, 'platforms',
'android-{0}'.format(self.android_api))
'android-{0}'.format(self.android_api))
if not self.buildozer.file_exists(android_platform):
packages = self._android_list_sdk()
android_package = 'android-{}'.format(self.android_api)
@ -406,12 +418,14 @@ class TargetAndroid(Target):
def install_platform(self):
cmd = self.buildozer.cmd
self.pa_dir = pa_dir = join(self.buildozer.platform_dir, 'python-for-android')
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')
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)
cwd=self.buildozer.platform_dir)
else:
cmd('git clone -b old_toolchain --single-branch '
'https://github.com/kivy/python-for-android.git',
@ -422,8 +436,7 @@ class TargetAndroid(Target):
source = self.buildozer.config.getdefault('app', 'android.branch')
if source:
cmd('git checkout %s' % (source),
cwd=pa_dir)
cmd('git checkout %s' % (source), cwd=pa_dir)
self._install_apache_ant()
self._install_android_sdk()
@ -439,11 +452,14 @@ class TargetAndroid(Target):
'ANDROIDSDK': self.android_sdk_dir,
'ANDROIDNDK': self.android_ndk_dir,
'ANDROIDAPI': self.android_api,
'ANDROIDNDKVER': 'r{}'.format(self.android_ndk_version)})
'ANDROIDNDKVER': 'r{}'.format(self.android_ndk_version)
})
def get_available_packages(self):
available_modules = self.buildozer.cmd(
'./distribute.sh -l', cwd=self.pa_dir, get_stdout=True)[0]
'./distribute.sh -l',
cwd=self.pa_dir,
get_stdout=True)[0]
if not available_modules.startswith('Available modules:'):
self.buildozer.error('Python-for-android invalid output for -l')
return available_modules[19:].splitlines()[0].split()
@ -451,16 +467,17 @@ class TargetAndroid(Target):
def compile_platform(self):
# for android, the compilation depends really on the app requirements.
# compile the distribution only if the requirements changed.
last_requirements = self.buildozer.state.get('android.requirements', '')
app_requirements = self.buildozer.config.getlist('app',
'requirements', '')
last_requirements = self.buildozer.state.get('android.requirements',
'')
app_requirements = self.buildozer.config.getlist('app', 'requirements',
'')
# we need to extract the requirements that python-for-android knows
# about
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]
android_requirements = [x for x in app_requirements
if onlyname(x) in available_modules]
need_compile = 0
if last_requirements != android_requirements:
@ -474,16 +491,17 @@ class TargetAndroid(Target):
need_compile = 1
# len('requirements.source.') == 20, so use name[20:]
source_dirs = {'P4A_{}_DIR'.format(name[20:]):
realpath(expanduser(value))
for name, value in self.buildozer.config.items('app')
if name.startswith('requirements.source.')}
source_dirs = {
'P4A_{}_DIR'.format(name[20:]): realpath(expanduser(value))
for name, value in self.buildozer.config.items('app')
if name.startswith('requirements.source.')
}
if source_dirs:
need_compile = 1
self.buildozer.environ.update(source_dirs)
self.buildozer.info('Using custom source dirs:\n {}'.format(
'\n '.join(['{} = {}'.format(k, v)
for k, v in source_dirs.items()])))
for k, v in source_dirs.items()])))
if not need_compile:
self.buildozer.info('Distribution already compiled, pass.')
@ -493,7 +511,7 @@ class TargetAndroid(Target):
cmd = self.buildozer.cmd
self.buildozer.debug('Clean and build python-for-android')
self.buildozer.rmdir(dist_dir) # Delete existing distribution to stop
# p4a complaining
# p4a complaining
cmd('./distribute.sh -m "{0}" -d "{1}"'.format(modules_str, dist_name),
cwd=self.pa_dir)
self.buildozer.debug('Remove temporary build files')
@ -515,7 +533,8 @@ class TargetAndroid(Target):
return package.lower()
def _generate_whitelist(self, dist_dir):
p4a_whitelist = self.buildozer.config.getlist('app', 'android.p4a_whitelist') or []
p4a_whitelist = self.buildozer.config.getlist(
'app', 'android.p4a_whitelist') or []
whitelist_fn = join(dist_dir, 'whitelist.txt')
with open(whitelist_fn, 'w') as fd:
for wl in p4a_whitelist:
@ -533,8 +552,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:
@ -563,21 +582,20 @@ class TargetAndroid(Target):
' --{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))
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))
# add permissions
permissions = config.getlist('app',
'android.permissions', [])
permissions = config.getlist('app', 'android.permissions', [])
for permission in permissions:
# force the latest component to be uppercase
permission = permission.split('.')
@ -608,22 +626,28 @@ class TargetAndroid(Target):
presplash = config.getdefault('app', 'presplash.filename', '')
if presplash:
build_cmd += ' --presplash {}'.format(join(self.buildozer.root_dir,
presplash))
presplash))
# add icon
icon = config.getdefault('app', 'icon.filename', '')
if icon:
build_cmd += ' --icon {}'.format(join(self.buildozer.root_dir, icon))
build_cmd += ' --icon {}'.format(join(self.buildozer.root_dir,
icon))
# OUYA Console support
ouya_category = config.getdefault('app', 'android.ouya.category', '').upper()
ouya_category = config.getdefault('app', 'android.ouya.category',
'').upper()
if ouya_category:
if ouya_category not in ('GAME', 'APP'):
raise SystemError('Invalid android.ouya.category: "{}" must be one of GAME or APP'.format(ouya_category))
raise SystemError(
'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))
ouya_icon = config.getdefault('app', 'android.ouya.icon.filename',
'')
build_cmd += ' --ouya-icon {}'.format(join(self.buildozer.root_dir,
ouya_icon))
# add orientation
orientation = config.getdefault('app', 'orientation', 'landscape')
@ -643,10 +667,11 @@ class TargetAndroid(Target):
# intent filters
intent_filters = config.getdefault('app',
'android.manifest.intent_filters', '')
'android.manifest.intent_filters',
'')
if intent_filters:
build_cmd += ' --intent-filters {}'.format(
join(self.buildozer.root_dir, intent_filters))
join(self.buildozer.root_dir, intent_filters))
# build only in debug right now.
if self.build_mode == 'debug':
@ -659,17 +684,19 @@ class TargetAndroid(Target):
# 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])
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)
title=apktitle,
version=version,
mode=mode)
# copy to our place
copyfile(join(dist_dir, 'bin', apk),
join(self.buildozer.bin_dir, apk))
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
@ -692,13 +719,16 @@ 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', '.'))
'app', 'android.library_references', [])
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))
if not self.buildozer.file_exists(ref):
self.buildozer.error('Invalid library reference (path not found): {}'.format(cref))
self.buildozer.error(
'Invalid library reference (path not found): {}'.format(
cref))
exit(1)
# get a relative path from the project file
ref = relpath(ref, realpath(dist_dir))
@ -734,7 +764,7 @@ class TargetAndroid(Target):
if serial:
return serial.split(',')
l = self.buildozer.cmd('{} devices'.format(self.adb_cmd),
get_stdout=True)[0].splitlines()
get_stdout=True)[0].splitlines()
serials = []
for serial in l:
if not serial:
@ -749,12 +779,10 @@ class TargetAndroid(Target):
super(TargetAndroid, self).cmd_deploy(*args)
state = self.buildozer.state
if 'android:latestapk' not in state:
self.buildozer.error(
'No APK built yet. Run "debug" first.')
self.buildozer.error('No APK built yet. Run "debug" first.')
if state.get('android:latestmode', '') != 'debug':
self.buildozer.error(
'Only debug APK are supported for deploy')
self.buildozer.error('Only debug APK are supported for deploy')
# search the APK in the bin dir
apk = state['android:latestapk']
@ -768,7 +796,8 @@ class TargetAndroid(Target):
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), cwd=self.buildozer.global_platform_dir)
self.adb_cmd, full_apk),
cwd=self.buildozer.global_platform_dir)
self.buildozer.environ.pop('ANDROID_SERIAL', None)
self.buildozer.info('Application pushed.')
@ -786,7 +815,9 @@ class TargetAndroid(Target):
self.buildozer.info('Run on {}'.format(serial))
self.buildozer.cmd(
'{adb} shell am start -n {package}/{entry} -a {entry}'.format(
adb=self.adb_cmd, package=package, entry=entrypoint),
adb=self.adb_cmd,
package=package,
entry=entrypoint),
cwd=self.buildozer.global_platform_dir)
self.buildozer.environ.pop('ANDROID_SERIAL', None)
@ -801,11 +832,10 @@ class TargetAndroid(Target):
return
self.buildozer.environ['ANDROID_SERIAL'] = serial[0]
self.buildozer.cmd('{adb} logcat'.format(adb=self.adb_cmd),
cwd=self.buildozer.global_platform_dir,
show_output=True)
cwd=self.buildozer.global_platform_dir,
show_output=True)
self.buildozer.environ.pop('ANDROID_SERIAL', None)
def get_target(buildozer):
return TargetAndroid(buildozer)