From 7091cf098c9d921f7f9a360658c9ae9c077ba0a9 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Sun, 24 Jan 2016 13:58:21 +0100 Subject: [PATCH] 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 --- buildozer/default.spec | 9 ++ buildozer/targets/android.py | 250 +++++++++++++++++-------------- buildozer/targets/android_new.py | 90 +++++++++++ 3 files changed, 235 insertions(+), 114 deletions(-) create mode 100644 buildozer/targets/android_new.py diff --git a/buildozer/default.spec b/buildozer/default.spec index c84195e..8153fa9 100644 --- a/buildozer/default.spec +++ b/buildozer/default.spec @@ -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 # diff --git a/buildozer/targets/android.py b/buildozer/targets/android.py index ed44d3a..953a1dd 100644 --- a/buildozer/targets/android.py +++ b/buildozer/targets/android.py @@ -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,9 +115,8 @@ class TargetAndroid(Target): self.keytool_cmd = self._locate_java('keytool') # Check for C header . - _, _, returncode_dpkg = self.buildozer.cmd( - 'dpkg --version', - break_on_error=False) + _, _, returncode_dpkg = self.buildozer.cmd('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'): @@ -192,15 +195,14 @@ class TargetAndroid(Target): return import _winreg with _winreg.OpenKey( - _winreg.HKEY_LOCAL_MACHINE, - r"SOFTWARE\JavaSoft\Java Development Kit") as jdk: #@UndefinedVariable + _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) @@ -432,7 +441,7 @@ class TargetAndroid(Target): show_output=False) if returncode != 1: self.buildozer.error('Aidl cannot be executed') - if sys.maxint > 2 ** 32: + if sys.maxint > 2**32: self.buildozer.error('') self.buildozer.error( 'You might have missed to install 32bits libs') @@ -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') - if system_p4a_dir: - cmd('ln -sf {} ./python-for-android'.format(system_p4a_dir), + self.p4a_directory) + system_p4a_dir = self.buildozer.config.getdefault('app', + 'android.p4a_dir') + if system_p4a_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: + 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) - else: - cmd('git clone -b old_toolchain --single-branch ' - 'https://github.com/kivy/python-for-android.git', - cwd=self.buildozer.platform_dir) - elif self.platform_update: - cmd('git clean -dxf', cwd=pa_dir) - cmd('git pull origin master', cwd=pa_dir) + elif self.platform_update: + cmd('git clean -dxf', 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,10 +499,9 @@ class TargetAndroid(Target): }) def get_available_packages(self): - available_modules = self.buildozer.cmd( - './distribute.sh -l', - cwd=self.pa_dir, - get_stdout=True)[0] + available_modules = self.buildozer.cmd('./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() @@ -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,60 +698,59 @@ 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, - version=version, - mode=mode) + 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) # copy to our place 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) diff --git a/buildozer/targets/android_new.py b/buildozer/targets/android_new.py new file mode 100644 index 0000000..aee88c9 --- /dev/null +++ b/buildozer/targets/android_new.py @@ -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)