From a4173ccedafab069f3bfe8424b668ca4e60b65f6 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Wed, 19 Dec 2012 02:34:32 +0100 Subject: [PATCH 01/32] first commit --- .gitignore | 27 ++++ README.rst | 42 ++++++ buildozer.py | 5 + buildozer/__init__.py | 183 +++++++++++++++++++++++++ buildozer/target.py | 12 ++ buildozer/targets/__init__.py | 0 buildozer/targets/android.py | 251 ++++++++++++++++++++++++++++++++++ buildozer/targets/ios.py | 36 +++++ 8 files changed, 556 insertions(+) create mode 100644 .gitignore create mode 100644 README.rst create mode 100755 buildozer.py create mode 100644 buildozer/__init__.py create mode 100644 buildozer/target.py create mode 100644 buildozer/targets/__init__.py create mode 100644 buildozer/targets/android.py create mode 100644 buildozer/targets/ios.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f24cd99 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +*.py[co] + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..ba4101b --- /dev/null +++ b/README.rst @@ -0,0 +1,42 @@ +Buildozer +========= + +THIS IS A WORK IN PROGRESS, DO NOT USE. + +Buildozer is a tool for creating application packages easilly. + +The goal is to have one "buildozer.spec" file in your app directory: it +describe your application requirements, titles, etc. Buildozer will use that +spec for create package for Android, iOS, Windows, OSX and Linux. + +Usage +----- + +#. Add buildozer repo into your PYTHONPATH. +#. Create a .spec +#. Go into your application directory and do:: + + buildozer.py -t android + +buildozer.spec +-------------- + +:: + [app] + + # Title of your application + title = My Application + + # Source code variables + source.dir = . + source.include_ext = py,png,jpg + + # Application versionning + version.regex = __version__ = '(.*)' + version.filename = %(source.dir)s/main.py + + # Application requirements + requirements = twisted kivy + + # Android specific + android.permissions = INTERNET diff --git a/buildozer.py b/buildozer.py new file mode 100755 index 0000000..f052341 --- /dev/null +++ b/buildozer.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +if __name__ == '__main__': + from buildozer import run + run() diff --git a/buildozer/__init__.py b/buildozer/__init__.py new file mode 100644 index 0000000..f8b1eb0 --- /dev/null +++ b/buildozer/__init__.py @@ -0,0 +1,183 @@ +''' + +Layout directory for buildozer: + + build/ + / + platform/ - all the platform files necessary + app/ - compiled application + + +''' + +import shelve +from sys import stdout, exit +from urllib import urlretrieve +from re import search +from ConfigParser import SafeConfigParser +from os.path import join, exists, dirname, realpath +from subprocess import Popen, PIPE +from os import environ, mkdir, unlink, rename +from copy import copy + +class Buildozer(object): + + def __init__(self, filename, target): + super(Buildozer, self).__init__() + self.environ = copy(environ) + self.targetname = target + self.specfilename = filename + self.state = None + self.config = SafeConfigParser() + self.config.read(filename) + + # resolve target + m = __import__('buildozer.targets.%s' % target, fromlist=['buildozer']) + self.target = m.get_target(self) + + def log(self, msg): + print '-', msg + + def error(self, msg): + print 'E', msg + exit(-1) + + def checkbin(self, msg, fn): + self.log('Search for {0}'.format(msg)) + if exists(fn): + return realpath(fn) + for dn in environ['PATH'].split(':'): + rfn = realpath(join(dn, fn)) + if exists(rfn): + self.log(' -> found at {0}'.format(rfn)) + return rfn + raise Exception(msg + 'not found') + + def cmd(self, command, **kwargs): + kwargs.setdefault('env', self.environ) + self.log('run %r' % command) + c = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, **kwargs) + ret = c.communicate() + if c.returncode != 0: + print '--- command failed' + print '-- stdout output' + print ret[0] + print '-- stderr output' + print ret[1] + print '--- end commend failed' + return ret + + def run(self): + self.log('Build started') + + self.log('check configuration tokens') + self.do_config_requirements() + + self.log('check requirements for %s' % self.targetname) + self.target.check_requirements() + + self.log('ensure build layout') + self.ensure_build_layout() + + self.log('install platform') + self.target.install_platform() + + self.log('compile platform') + self.target.compile_platform() + + def do_config_requirements(self): + pass + + def ensure_build_layout(self): + specdir = dirname(self.specfilename) + self.mkdir(join(specdir, self.targetname)) + self.mkdir(join(specdir, self.targetname, 'platform')) + self.mkdir(join(specdir, self.targetname, 'app')) + self.state = shelve.open(join(self.platform_dir, 'state.db')) + + def mkdir(self, dn): + if exists(dn): + return + mkdir(dn) + + def file_exists(self, *args): + return exists(join(*args)) + + def file_rename(self, source, target, cwd=None): + if cwd: + source = join(cwd, source) + target = join(cwd, target) + rename(source, target) + + def download(self, url, filename, cwd=None): + def report_hook(index, blksize, size): + if size <= 0: + progression = '{0} bytes'.format(index * blksize) + else: + progression = '{0:.2f}%'.format( + index * blksize * 100. / float(size)) + print '- Download', progression, '\r', + stdout.flush() + + url = url + filename + if cwd: + filename = join(cwd, filename) + if self.file_exists(filename): + unlink(filename) + + self.log('Downloading {0}'.format(url)) + urlretrieve(url, filename, report_hook) + return filename + + def get_version(self): + c = self.config + has_version = c.has_option('app', 'version') + has_regex = c.has_option('app', 'version.regex') + has_filename = c.has_option('app', 'version.filename') + + # version number specified + if has_version: + if has_regex or has_filename: + raise Exception( + 'version.regex and version.filename conflict with version') + return c.get('app', 'version') + + # search by regex + if has_regex or has_filename: + if has_regex and not has_filename: + raise Exception('version.filename is missing') + if has_filename and not has_regex: + raise Exception('version.regex is missing') + + fn = c.get('app', 'filename') + with fn as fd: + data = fd.read() + regex = c.get('app', 'version.regex') + match = search(regex, data) + if not match: + raise Exception( + 'Unable to found capture version in %r' % fn) + return match[0] + + raise Exception('Missing version or version.regex + version.filename') + + @property + def platform_dir(self): + return join(dirname(self.specfilename), self.targetname, 'platform') + + @property + def app_dir(self): + return join(dirname(self.specfilename), self.targetname, 'app') + +def run(): + from optparse import OptionParser + + parser = OptionParser() + parser.add_option('-t', '--target', dest='target', + help='target to build (android, ios, windows, linux, osx)') + + (options, args) = parser.parse_args() + + if options.target is None: + raise Exception('Missing -t TARGET') + Buildozer('buildozer.spec', options.target).run() diff --git a/buildozer/target.py b/buildozer/target.py new file mode 100644 index 0000000..7d0fcd6 --- /dev/null +++ b/buildozer/target.py @@ -0,0 +1,12 @@ + +class Target(object): + + def __init__(self, buildozer): + super(Target, self).__init__() + self.buildozer = buildozer + + def check_requirements(self): + pass + + def compile_platform(self): + pass diff --git a/buildozer/targets/__init__.py b/buildozer/targets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/buildozer/targets/android.py b/buildozer/targets/android.py new file mode 100644 index 0000000..470dfe9 --- /dev/null +++ b/buildozer/targets/android.py @@ -0,0 +1,251 @@ +ANDROID_API = '16' +ANDROID_SDK_VERSION = '21' +ANDROID_NDK_VERSION = '8c' +APACHE_ANT_VERSION = '1.8.4' + +import tarfile +import zipfile +import traceback +from sys import platform +from buildozer.target import Target +from os.path import join, realpath + + +class TargetAndroid(Target): + + def check_requirements(self): + if platform in ('win32', 'cygwin'): + try: + self._set_win32_java_home() + except: + traceback.print_exc() + self.android_cmd = join('android-sdk', 'tools', 'android.bat') + self.ant_cmd = join('apache-ant', 'bin', 'ant.bat') + self.adb_cmd = join('android-sdk', 'platform-tools', 'adb.exe') + self.javac_cmd = self._locate_java('javac.exe') + self.keytool_cmd = self._locate_java('keytool.exe') + elif platform in ('darwin', ): + self.android_cmd = join('android-sdk', 'tools', 'android') + self.ant_cmd = join('apache-ant', 'bin', 'ant') + self.adb_cmd = join('android-sdk', 'platform-tools', 'adb') + self.javac_cmd = self._locate_java('javac') + self.keytool_cmd = self._locate_java('keytool') + else: + self.android_cmd = join('android-sdk', 'tools', 'android') + self.ant_cmd = join('apache-ant', 'bin', 'ant') + self.adb_cmd = join('android-sdk', 'platform-tools', 'adb') + self.javac_cmd = self._locate_java('javac') + self.keytool_cmd = self._locate_java('keytool') + + checkbin = self.buildozer.checkbin + checkbin('Git git', 'git') + checkbin('Cython cython', 'cython') + checkbin('Java compiler', self.javac_cmd) + checkbin('Java keytool', self.keytool_cmd) + + def _set_win32_java_home(self): + 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 + self.buildozer.environ['JAVA_HOME'] = java_home + + def _locate_java(self, s): + '''If JAVA_HOME is in the environ, return $JAVA_HOME/bin/s. Otherwise, + return s. + ''' + if 'JAVA_HOME' in self.buildozer.environ: + return join(self.buildozer.environ['JAVA_HOME'], 'bin', s) + else: + return s + + def _install_apache_ant(self): + ant_dir = join(self.buildozer.platform_dir, 'apache-ant') + if self.buildozer.file_exists(ant_dir): + self.buildozer.log('Apache ANT found at {0}'.format(ant_dir)) + return ant_dir + + self.buildozer.log('Android ANT is missing, downloading') + archive = 'apache-ant-{0}-bin.tar.gz'.format(APACHE_ANT_VERSION) + unpacked = 'apache-ant-{0}'.format(APACHE_ANT_VERSION) + url = 'http://archive.apache.org/dist/ant/binaries/' + archive = self.buildozer.download(url, archive, + cwd=self.buildozer.platform_dir) + + tf = tarfile.open(archive, 'r:*') + tf.extractall(path=self.buildozer.platform_dir) + tf.close() + + self.buildozer.file_rename(unpacked, 'apache-ant', + cwd=self.buildozer.platform_dir) + self.buildozer.log('Apache ANT installation done.') + return ant_dir + + def _install_android_sdk(self): + sdk_dir = join(self.buildozer.platform_dir, 'android-sdk') + if self.buildozer.file_exists(sdk_dir): + self.buildozer.log('Android SDK found at {0}'.format(sdk_dir)) + return sdk_dir + + self.buildozer.log('Android SDK is missing, downloading') + if platform in ('win32', 'cygwin'): + archive = 'android-sdk_r{0}-windows.zip' + unpacked = 'android-sdk-windows' + elif platform in ('darwin', ): + archive = 'android-sdk_r{0}-macosx.zip' + unpacked = 'android-sdk-macosx' + elif platform in ('linux2', 'linux3'): + archive = 'android-sdk_r{0}-linux.tgz' + unpacked = 'android-sdk-linux' + else: + raise SystemError('Unsupported platform: {0}'.format(platform)) + + archive = archive.format(ANDROID_SDK_VERSION) + url = 'http://dl.google.com/android/' + archive = self.buildozer.download(url, archive, + cwd=self.buildozer.platform_dir) + + self.buildozer.log('Unpacking Android SDK') + if archive.endswith('.tgz'): + tf = tarfile.open(archive, 'r:*') + tf.extractall(path=self.buildozer.platform_dir) + tf.close() + else: + zf = zipfile.ZipFile(archive) + zf.extractall(path=self.buildozer.platform_dir) + zf.close() + + self.buildozer.file_rename(unpacked, 'android-sdk', + cwd=self.buildozer.platform_dir) + self.buildozer.log('Android SDK installation done.') + return sdk_dir + + def _install_android_ndk(self): + ndk_dir = join(self.buildozer.platform_dir, 'android-ndk') + if self.buildozer.file_exists(ndk_dir): + self.buildozer.log('Android NDK found at {0}'.format(ndk_dir)) + return ndk_dir + + self.buildozer.log('Android NDK is missing, downloading') + if platform in ('win32', 'cygwin'): + archive = 'android-ndk-r{0}-windows.zip' + elif platform in ('darwin', ): + archive = 'android-ndk-r{0}-darwin.tar.bz2' + elif platform in ('linux2', 'linux3'): + archive = 'android-ndk-r{0}-linux-x86.tar.bz2' + else: + raise SystemError('Unsupported platform: {0}'.format(platform)) + + unpacked = 'android-ndk-r{0}' + archive = archive.format(ANDROID_NDK_VERSION) + unpacked = unpacked.format(ANDROID_NDK_VERSION) + url = 'http://dl.google.com/android/ndk/' + archive = self.buildozer.download(url, archive, + cwd=self.buildozer.platform_dir) + + self.buildozer.log('Unpacking Android NDK') + if archive.endswith('.tar.bz2'): + tf = tarfile.open(archive, 'r:*') + tf.extractall(path=self.buildozer.platform_dir) + tf.close() + else: + zf = zipfile.ZipFile(archive) + zf.extractall(path=self.buildozer.platform_dir) + zf.close() + + self.buildozer.file_rename(unpacked, 'android-ndk', + cwd=self.buildozer.platform_dir) + self.buildozer.log('Android NDK installation done.') + return ndk_dir + + def _install_android_packages(self): + packages = [] + android_platform = join(self.sdk_dir, 'platforms', + 'android-{0}'.format(ANDROID_API)) + if not self.buildozer.file_exists(android_platform): + packages.append('android-{0}'.format(ANDROID_API)) + if not self.buildozer.file_exists(self.sdk_dir, 'platform-tools'): + packages.append('platform-tools') + if not packages: + self.buildozer.log('Android packages already installed.') + return + ret = self.buildozer.cmd('{0} update sdk -u -a -t {1}'.format( + self.android_cmd, ','.join(packages)), + cwd=self.buildozer.platform_dir) + print ret[0] + print ret[1] + self.buildozer.log('Android packages installation done.') + + 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): + cmd('git clone git://github.com/kivy/python-for-android', + cwd=self.buildozer.platform_dir) + ''' + # don't update the latest clone, except if we asked for it. + else: + cmd('git clean -dxf', cwd=pa_dir) + cmd('git pull origin master', cwd=pa_dir) + ''' + + self.ant_dir = ant_dir = self._install_apache_ant() + self.sdk_dir = sdk_dir = self._install_android_sdk() + self.ndk_dir = ndk_dir = self._install_android_ndk() + self._install_android_packages() + self.buildozer.environ.update({ + 'ANDROIDSDK': realpath(sdk_dir), + 'ANDROIDNDK': realpath(ndk_dir), + 'ANDROIDAPI': ANDROID_API, + 'ANDROIDNDKVER': ANDROID_NDK_VERSION}) + + 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.get('app', + 'requirements', '').split() + + # we need to extract the requirements that python-for-android knows + # about + available_modules = self.buildozer.cmd( + './distribute.sh -l', cwd=self.pa_dir)[0] + if not available_modules.startswith('Available modules:'): + self.buildozer.error('Python-for-android invalid output for -l') + available_modules = available_modules[19:].splitlines()[0].split() + + android_requirements = [x for x in app_requirements if x in + available_modules] + missing_requirements = [x for x in app_requirements if x not in + available_modules] + + if missing_requirements: + self.buildozer.error( + 'Cannot package the app cause of the missing' + ' requirements in python-for-android: {0}'.format( + missing_requirements)) + + need_compile = 0 + if last_requirements != android_requirements: + need_compile = 1 + if not self.buildozer.file_exists(self.pa_dir, 'dist', 'default'): + need_compile = 1 + + if not need_compile: + self.buildozer.log('Distribution already compiled, pass.') + return + + modules_str = ' '.join(android_requirements) + cmd = self.buildozer.cmd + cmd('git clean -dxf', cwd=self.pa_dir) + cmd('./distribute.sh -m "{0}"'.format(modules_str), cwd=self.pa_dir) + self.buildozer.log('Distribution compiled.') + + + + +def get_target(buildozer): + return TargetAndroid(buildozer) diff --git a/buildozer/targets/ios.py b/buildozer/targets/ios.py new file mode 100644 index 0000000..c8fb735 --- /dev/null +++ b/buildozer/targets/ios.py @@ -0,0 +1,36 @@ +from buildozer.target import Target + +class TargetIos(Target): + + def check_requirements(self): + checkbin = self.buildozer.checkbin + cmd = self.buildozer.cmd + + checkbin('Xcode xcodebuild', 'xcodebuild') + checkbin('Xcode xcode-select', 'xcode-select') + checkbin('Git git', 'git') + + print 'Check availability of a iPhone SDK' + sdk = cmd('xcodebuild -showsdks | fgrep "iphoneos" | tail -n 1 | awk \'{print $2}\'')[0] + if not sdk: + raise Exception( + 'No iPhone SDK found. Please install at least one iOS SDK.') + else: + print ' -> found %r' % sdk + + print 'Check Xcode path' + xcode = cmd('xcode-select -print-path')[0] + if not xcode: + raise Exception('Unable to get xcode path') + print ' -> found %r' % xcode + + def install_platform(self): + cmd = self.buildozer.cmd + cmd('git clone git://github.com/kivy/kivy-ios', + cwd=self.buildozer.platform_dir) + + + + +def get_target(buildozer): + return TargetIos(buildozer) From da7c95b6ee2fe084cdac1c72df679280e9399bea Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Wed, 19 Dec 2012 02:35:29 +0100 Subject: [PATCH 02/32] typo --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index ba4101b..08d6285 100644 --- a/README.rst +++ b/README.rst @@ -22,6 +22,7 @@ buildozer.spec -------------- :: + [app] # Title of your application @@ -40,3 +41,4 @@ buildozer.spec # Android specific android.permissions = INTERNET + From 3dc42cb6273fedd6701d5a5721b16c6d51d89998 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Wed, 19 Dec 2012 03:55:11 +0100 Subject: [PATCH 03/32] wip --- README.rst | 11 +++- buildozer/__init__.py | 121 ++++++++++++++++++++++++++++++----- buildozer/targets/android.py | 60 ++++++++--------- 3 files changed, 140 insertions(+), 52 deletions(-) diff --git a/README.rst b/README.rst index 08d6285..4338ff0 100644 --- a/README.rst +++ b/README.rst @@ -28,16 +28,21 @@ buildozer.spec # Title of your application title = My Application - # Source code variables + # Source code where the main.py live source.dir = . - source.include_ext = py,png,jpg + + # Source files to include (let empty to include all the files) + source.include_exts = py,png,jpg + + # Source files to exclude (let empty to not excluding anything) + #source.exclude_exts = spec # Application versionning version.regex = __version__ = '(.*)' version.filename = %(source.dir)s/main.py # Application requirements - requirements = twisted kivy + requirements = twisted,kivy # Android specific android.permissions = INTERNET diff --git a/buildozer/__init__.py b/buildozer/__init__.py index f8b1eb0..8f7a44b 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -11,30 +11,50 @@ Layout directory for buildozer: ''' import shelve +import zipfile from sys import stdout, exit from urllib import urlretrieve from re import search from ConfigParser import SafeConfigParser -from os.path import join, exists, dirname, realpath +from os.path import join, exists, dirname, realpath, splitext from subprocess import Popen, PIPE -from os import environ, mkdir, unlink, rename +from os import environ, mkdir, unlink, rename, walk, sep from copy import copy +from shutil import copyfile + +class ConfigDict(dict): + def get(self, key, value, default): + print 'hello' + class Buildozer(object): def __init__(self, filename, target): super(Buildozer, self).__init__() - self.environ = copy(environ) + self.environ = {} self.targetname = target self.specfilename = filename self.state = None - self.config = SafeConfigParser() + self.config = SafeConfigParser({}, ConfigDict, allow_no_value=True) + self.config.getlist = self._get_config_list + self.config.getdefault = self._get_config_default self.config.read(filename) # resolve target m = __import__('buildozer.targets.%s' % target, fromlist=['buildozer']) self.target = m.get_target(self) + def _get_config_list(self, section, token, default=None): + values = self.config.getdefault(section, token, default).split(',') + return [x.strip() for x in values] + + def _get_config_default(self, section, token, default=None): + if not self.config.has_section(section): + return default + if not self.config.has_option(section, token): + return default + return self.config.get(section, token) + def log(self, msg): print '-', msg @@ -54,7 +74,11 @@ class Buildozer(object): raise Exception(msg + 'not found') def cmd(self, command, **kwargs): - kwargs.setdefault('env', self.environ) + #print ' '.join(['{0}={1}'.format(*args) for args in + # self.environ.iteritems()]) + env = copy(environ) + env.update(self.environ) + kwargs.setdefault('env', env) self.log('run %r' % command) c = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, **kwargs) ret = c.communicate() @@ -70,29 +94,35 @@ class Buildozer(object): def run(self): self.log('Build started') - self.log('check configuration tokens') + self.log('Check configuration tokens') self.do_config_requirements() - self.log('check requirements for %s' % self.targetname) + self.log('Check requirements for %s' % self.targetname) self.target.check_requirements() - self.log('ensure build layout') + self.log('Ensure build layout') self.ensure_build_layout() - self.log('install platform') + self.log('Install platform') self.target.install_platform() - self.log('compile platform') + self.log('Compile platform') self.target.compile_platform() + self.log('Prebuild the application') + self.prebuild_application() + + self.log('Package the application') + self.target.build_package() + def do_config_requirements(self): pass def ensure_build_layout(self): specdir = dirname(self.specfilename) - self.mkdir(join(specdir, self.targetname)) - self.mkdir(join(specdir, self.targetname, 'platform')) - self.mkdir(join(specdir, self.targetname, 'app')) + self.mkdir(join(specdir, '.buildozer', self.targetname)) + self.mkdir(join(specdir, '.buildozer', self.targetname, 'platform')) + self.mkdir(join(specdir, '.buildozer', self.targetname, 'app')) self.state = shelve.open(join(self.platform_dir, 'state.db')) def mkdir(self, dn): @@ -109,6 +139,28 @@ class Buildozer(object): target = join(cwd, target) rename(source, target) + def file_extract(self, archive, cwd=None): + if archive.endswith('.tgz') or archive.endswith('.tar.gz'): + # XXX tarfile doesn't work for NDK-r8c :( + #tf = tarfile.open(archive, 'r:*') + #tf.extractall(path=cwd) + #tf.close() + self.cmd('tar xzf {0}'.format(archive), cwd=cwd) + return + + if archive.endswith('.tbz2') or archive.endswith('.tar.bz2'): + # XXX same as before + self.cmd('tar xjf {0}'.format(archive), cwd=cwd) + return + + if archive.endswith('.zip'): + zf = zipfile.ZipFile(archive) + zf.extractall(path=cwd) + zf.close() + return + + raise Exception('Unhandled extraction for type {0}'.format(archive)) + def download(self, url, filename, cwd=None): def report_hook(index, blksize, size): if size <= 0: @@ -161,13 +213,52 @@ class Buildozer(object): raise Exception('Missing version or version.regex + version.filename') + def prebuild_application(self): + source_dir = realpath(self.config.getdefault('app', 'source.dir', '.')) + include_exts = self.config.getlist('app', 'source.include_exts', '') + exclude_exts = self.config.getlist('app', 'source.exclude_exts', '') + app_dir = self.app_dir + + for root, dirs, files in walk(source_dir): + # avoid hidden directory + if True in [x.startswith('.') for x in root.split(sep)]: + continue + + for fn in files: + # avoid hidden files + if fn.startswith('.'): + continue + + # filter based on the extension + # TODO more filters + basename, ext = splitext(fn) + if ext: + ext = ext[1:] + if include_exts and ext not in include_exts: + continue + if exclude_exts and ext in exclude_exts: + continue + + sfn = join(root, fn) + rfn = realpath(join(app_dir, root[len(source_dir):], fn)) + + # ensure the directory exists + dfn = dirname(rfn) + self.mkdir(dfn) + + # copy! + self.log('Copy {0}'.format(sfn)) + copyfile(sfn, rfn) + @property def platform_dir(self): - return join(dirname(self.specfilename), self.targetname, 'platform') + return join(dirname(self.specfilename), '.buildozer', + self.targetname, 'platform') @property def app_dir(self): - return join(dirname(self.specfilename), self.targetname, 'app') + return join(dirname(self.specfilename), '.buildozer', + self.targetname, 'app') def run(): from optparse import OptionParser diff --git a/buildozer/targets/android.py b/buildozer/targets/android.py index 470dfe9..a7b4e93 100644 --- a/buildozer/targets/android.py +++ b/buildozer/targets/android.py @@ -1,10 +1,16 @@ -ANDROID_API = '16' +# +# Android target +# Thanks for Renpy (again) for its install_sdk.py and plat.py in the PGS4A +# project! +# + + +ANDROID_API = '14' ANDROID_SDK_VERSION = '21' ANDROID_NDK_VERSION = '8c' APACHE_ANT_VERSION = '1.8.4' -import tarfile -import zipfile + import traceback from sys import platform from buildozer.target import Target @@ -72,13 +78,10 @@ class TargetAndroid(Target): archive = 'apache-ant-{0}-bin.tar.gz'.format(APACHE_ANT_VERSION) unpacked = 'apache-ant-{0}'.format(APACHE_ANT_VERSION) url = 'http://archive.apache.org/dist/ant/binaries/' - archive = self.buildozer.download(url, archive, + self.buildozer.download(url, archive, cwd=self.buildozer.platform_dir) - tf = tarfile.open(archive, 'r:*') - tf.extractall(path=self.buildozer.platform_dir) - tf.close() - + self.buildozer.file_extract(archive, cwd=self.buildozer.platform_dir) self.buildozer.file_rename(unpacked, 'apache-ant', cwd=self.buildozer.platform_dir) self.buildozer.log('Apache ANT installation done.') @@ -105,19 +108,11 @@ class TargetAndroid(Target): archive = archive.format(ANDROID_SDK_VERSION) url = 'http://dl.google.com/android/' - archive = self.buildozer.download(url, archive, + self.buildozer.download(url, archive, cwd=self.buildozer.platform_dir) self.buildozer.log('Unpacking Android SDK') - if archive.endswith('.tgz'): - tf = tarfile.open(archive, 'r:*') - tf.extractall(path=self.buildozer.platform_dir) - tf.close() - else: - zf = zipfile.ZipFile(archive) - zf.extractall(path=self.buildozer.platform_dir) - zf.close() - + self.buildozer.file_extract(archive, cwd=self.buildozer.platform_dir) self.buildozer.file_rename(unpacked, 'android-sdk', cwd=self.buildozer.platform_dir) self.buildozer.log('Android SDK installation done.') @@ -143,19 +138,11 @@ class TargetAndroid(Target): archive = archive.format(ANDROID_NDK_VERSION) unpacked = unpacked.format(ANDROID_NDK_VERSION) url = 'http://dl.google.com/android/ndk/' - archive = self.buildozer.download(url, archive, + self.buildozer.download(url, archive, cwd=self.buildozer.platform_dir) self.buildozer.log('Unpacking Android NDK') - if archive.endswith('.tar.bz2'): - tf = tarfile.open(archive, 'r:*') - tf.extractall(path=self.buildozer.platform_dir) - tf.close() - else: - zf = zipfile.ZipFile(archive) - zf.extractall(path=self.buildozer.platform_dir) - zf.close() - + self.buildozer.file_extract(archive, cwd=self.buildozer.platform_dir) self.buildozer.file_rename(unpacked, 'android-ndk', cwd=self.buildozer.platform_dir) self.buildozer.log('Android NDK installation done.') @@ -172,11 +159,9 @@ class TargetAndroid(Target): if not packages: self.buildozer.log('Android packages already installed.') return - ret = self.buildozer.cmd('{0} update sdk -u -a -t {1}'.format( + self.buildozer.cmd('{0} update sdk -u -a -t {1}'.format( self.android_cmd, ','.join(packages)), cwd=self.buildozer.platform_dir) - print ret[0] - print ret[1] self.buildozer.log('Android packages installation done.') def install_platform(self): @@ -192,7 +177,7 @@ class TargetAndroid(Target): cmd('git pull origin master', cwd=pa_dir) ''' - self.ant_dir = ant_dir = self._install_apache_ant() + self._install_apache_ant() self.sdk_dir = sdk_dir = self._install_android_sdk() self.ndk_dir = ndk_dir = self._install_android_ndk() self._install_android_packages() @@ -206,8 +191,8 @@ class TargetAndroid(Target): # 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.get('app', - 'requirements', '').split() + app_requirements = self.buildozer.config.getlist('app', + 'requirements', '') # we need to extract the requirements that python-for-android knows # about @@ -244,7 +229,14 @@ class TargetAndroid(Target): cmd('./distribute.sh -m "{0}"'.format(modules_str), cwd=self.pa_dir) self.buildozer.log('Distribution compiled.') + # ensure we will not compile again + self.buildozer.state['android.requirements'] = android_requirements + self.buildozer.state.sync() + def build_package(self): + dist_dir = join(self.pa_dir, 'dist', 'default') + #cmd('./build.py --name {0} --private {1} ' + # '--version {2} def get_target(buildozer): From 2e68748411449b485c5588d401e89e4c2eae9bf6 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Wed, 19 Dec 2012 13:25:37 +0100 Subject: [PATCH 04/32] finish buildozer android target (only debug build are supported right now.) --- README.rst | 21 +++++++++++++- buildozer/__init__.py | 30 ++++++++++++-------- buildozer/targets/android.py | 53 ++++++++++++++++++++++++++++++++++-- 3 files changed, 88 insertions(+), 16 deletions(-) diff --git a/README.rst b/README.rst index 4338ff0..1c97812 100644 --- a/README.rst +++ b/README.rst @@ -28,6 +28,12 @@ buildozer.spec # Title of your application title = My Application + # Package name + package.name = myapp + + # Package domain (needed for android/ios packaging) + package.domain = org.test + # Source code where the main.py live source.dir = . @@ -37,13 +43,26 @@ buildozer.spec # Source files to exclude (let empty to not excluding anything) #source.exclude_exts = spec - # Application versionning + # Application versionning (method 1) version.regex = __version__ = '(.*)' version.filename = %(source.dir)s/main.py + # Application versionning (method 2) + # version = 1.2.0 + # Application requirements requirements = twisted,kivy + # # Android specific + # + + # Permissions android.permissions = INTERNET + # Minimum SDK allowed for installation + android.minsdk = 8 + + # Android SDK to use + android.sdk = 16 + diff --git a/buildozer/__init__.py b/buildozer/__init__.py index 8f7a44b..f0737ec 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -22,10 +22,6 @@ from os import environ, mkdir, unlink, rename, walk, sep from copy import copy from shutil import copyfile -class ConfigDict(dict): - def get(self, key, value, default): - print 'hello' - class Buildozer(object): @@ -35,7 +31,7 @@ class Buildozer(object): self.targetname = target self.specfilename = filename self.state = None - self.config = SafeConfigParser({}, ConfigDict, allow_no_value=True) + self.config = SafeConfigParser() self.config.getlist = self._get_config_list self.config.getdefault = self._get_config_default self.config.read(filename) @@ -123,6 +119,7 @@ class Buildozer(object): self.mkdir(join(specdir, '.buildozer', self.targetname)) self.mkdir(join(specdir, '.buildozer', self.targetname, 'platform')) self.mkdir(join(specdir, '.buildozer', self.targetname, 'app')) + self.mkdir(join(specdir, 'bin')) self.state = shelve.open(join(self.platform_dir, 'state.db')) def mkdir(self, dn): @@ -201,15 +198,17 @@ class Buildozer(object): if has_filename and not has_regex: raise Exception('version.regex is missing') - fn = c.get('app', 'filename') - with fn as fd: + fn = c.get('app', 'version.filename') + with open(fn) as fd: data = fd.read() regex = c.get('app', 'version.regex') match = search(regex, data) if not match: raise Exception( 'Unable to found capture version in %r' % fn) - return match[0] + version = match.groups()[0] + self.log('Captured version: {0}'.format(version)) + return version raise Exception('Missing version or version.regex + version.filename') @@ -252,13 +251,20 @@ class Buildozer(object): @property def platform_dir(self): - return join(dirname(self.specfilename), '.buildozer', - self.targetname, 'platform') + return realpath( + join(dirname(self.specfilename), '.buildozer', + self.targetname, 'platform')) @property def app_dir(self): - return join(dirname(self.specfilename), '.buildozer', - self.targetname, 'app') + return realpath(join( + dirname(self.specfilename), '.buildozer', + self.targetname, 'app')) + + @property + def bin_dir(self): + return realpath(join( + dirname(self.specfilename), 'bin')) def run(): from optparse import OptionParser diff --git a/buildozer/targets/android.py b/buildozer/targets/android.py index a7b4e93..32ec418 100644 --- a/buildozer/targets/android.py +++ b/buildozer/targets/android.py @@ -12,9 +12,11 @@ APACHE_ANT_VERSION = '1.8.4' import traceback -from sys import platform +from pipes import quote +from sys import platform, executable from buildozer.target import Target from os.path import join, realpath +from shutil import copyfile class TargetAndroid(Target): @@ -235,8 +237,53 @@ class TargetAndroid(Target): def build_package(self): dist_dir = join(self.pa_dir, 'dist', 'default') - #cmd('./build.py --name {0} --private {1} ' - # '--version {2} + config = self.buildozer.config + package_domain = config.getdefault('app', 'package.domain', '') + package = config.get('app', 'package.name') + if package_domain: + package = package_domain + '.' + package + version = self.buildozer.get_version() + + build_cmd = ( + '{python} build.py --name {name}' + ' --version {version}' + ' --package {package}' + ' --private {appdir}' + ' --sdk {androidsdk}' + ' --minsdk {androidminsdk}').format( + python=executable, + name=quote(config.get('app', 'title')), + version=version, + package=package, + appdir=self.buildozer.app_dir, + androidminsdk=config.getdefault( + 'app', 'android.minsdk', 8), + androidsdk=config.getdefault( + 'app', 'android.sdk', ANDROID_API)) + + # add permissions + permissions = config.getlist('app', + 'android.permissions', []) + for permission in permissions: + build_cmd += ' --permission {0}'.format(permission) + + # build only in debug right now. + build_cmd += ' debug' + self.buildozer.cmd(build_cmd, cwd=dist_dir) + + # 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}-debug.apk'.format( + title=apktitle, version=version) + + # copy to our place + copyfile(join(dist_dir, 'bin', apk), + join(self.buildozer.bin_dir, apk)) + + self.buildozer.log('Android packaging done!') + self.buildozer.log('APK {0} available in the bin directory'.format(apk)) def get_target(buildozer): From 66f6e46c4f523cc2b3be20b7dd8ee9d2f3d648a5 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Wed, 19 Dec 2012 13:27:46 +0100 Subject: [PATCH 05/32] add more doc --- README.rst | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.rst b/README.rst index 1c97812..1765cee 100644 --- a/README.rst +++ b/README.rst @@ -25,44 +25,44 @@ buildozer.spec [app] - # Title of your application + # (str) Title of your application title = My Application - # Package name + # (str) Package name package.name = myapp - # Package domain (needed for android/ios packaging) + # (str) Package domain (needed for android/ios packaging) package.domain = org.test - # Source code where the main.py live + # (str) Source code where the main.py live source.dir = . - # Source files to include (let empty to include all the files) + # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg - # Source files to exclude (let empty to not excluding anything) + # (list) Source files to exclude (let empty to not excluding anything) #source.exclude_exts = spec - # Application versionning (method 1) + # (str) Application versionning (method 1) version.regex = __version__ = '(.*)' version.filename = %(source.dir)s/main.py - # Application versionning (method 2) + # (str) Application versionning (method 2) # version = 1.2.0 - # Application requirements + # (list) Application requirements requirements = twisted,kivy # # Android specific # - # Permissions - android.permissions = INTERNET + # (list) Permissions + #android.permissions = INTERNET - # Minimum SDK allowed for installation - android.minsdk = 8 + # (int) Minimum SDK allowed for installation + #android.minsdk = 8 - # Android SDK to use - android.sdk = 16 + # (int) Android SDK to use + #android.sdk = 16 From 262922472f3eee7dfabbe3cecd651f7234052dce Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Wed, 19 Dec 2012 17:35:48 +0100 Subject: [PATCH 06/32] rework command line arguments / target / usage add deploy and run command. --- README.rst | 70 +++++++++++- buildozer/__init__.py | 203 +++++++++++++++++++++++++++-------- buildozer/default.spec | 46 ++++++++ buildozer/target.py | 57 ++++++++++ buildozer/targets/android.py | 71 ++++++++++-- buildozer/targets/ios.py | 3 + 6 files changed, 396 insertions(+), 54 deletions(-) create mode 100644 buildozer/default.spec diff --git a/README.rst b/README.rst index 1765cee..eb07114 100644 --- a/README.rst +++ b/README.rst @@ -9,18 +9,79 @@ The goal is to have one "buildozer.spec" file in your app directory: it describe your application requirements, titles, etc. Buildozer will use that spec for create package for Android, iOS, Windows, OSX and Linux. -Usage ------ +Usage example +------------- #. Add buildozer repo into your PYTHONPATH. #. Create a .spec #. Go into your application directory and do:: - buildozer.py -t android + buildozer.py init + # edit the buildozer.spec, then + buildozer.py android build + +Example of commands:: + + # buildozer commands + buildozer.py clean + + # buildozer target command + buildozer.py android update + buildozer.py android install + buildozer.py android debug + buildozer.py android release + + # or all in one (compile in debug, install on device) + buildozer.py android debug install + + # set the default command if nothing set + buildozer.py setdefault android debug install + + +Usage +----- + +:: + + Usage: buildozer [target] [command1] [command2] + + Available targets: + android + Android target, based on python-for-android project + ios + iOS target, based on kivy-ios project. (not working yet.) + + Global commands (without target): + clean + Clean the whole Buildozer environment. + help + Show the Buildozer help. + init + Create a initial buildozer.spec in the current directory + setdefault + Set the default command to do when to arguments are given + + Target commands: + clean + Clean the target environment + update + Update the target dependencies + debug + Build the application in debug mode + release + Build the application in release mode + deploy + Install the application on the device + run + Run the application on the device + + buildozer.spec -------------- +See buildozer/default.spec for an up-to-date spec file. + :: [app] @@ -66,3 +127,6 @@ buildozer.spec # (int) Android SDK to use #android.sdk = 16 + # (str) Android entry point, default is ok for Kivy-based app + #android.entrypoint = org.renpy.android.PythonActivity + diff --git a/buildozer/__init__.py b/buildozer/__init__.py index f0737ec..67ecb7e 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -12,23 +12,23 @@ Layout directory for buildozer: import shelve import zipfile +import sys from sys import stdout, exit from urllib import urlretrieve from re import search from ConfigParser import SafeConfigParser from os.path import join, exists, dirname, realpath, splitext from subprocess import Popen, PIPE -from os import environ, mkdir, unlink, rename, walk, sep +from os import environ, mkdir, unlink, rename, walk, sep, listdir from copy import copy -from shutil import copyfile +from shutil import copyfile, rmtree class Buildozer(object): - def __init__(self, filename, target): + def __init__(self, filename='buildozer.spec', target=None): super(Buildozer, self).__init__() self.environ = {} - self.targetname = target self.specfilename = filename self.state = None self.config = SafeConfigParser() @@ -36,7 +36,13 @@ class Buildozer(object): self.config.getdefault = self._get_config_default self.config.read(filename) - # resolve target + self.targetname = None + self.target = None + if target: + self.set_target(target) + + def set_target(self, target): + self.targetname = target m = __import__('buildozer.targets.%s' % target, fromlist=['buildozer']) self.target = m.get_target(self) @@ -56,7 +62,7 @@ class Buildozer(object): def error(self, msg): print 'E', msg - exit(-1) + exit(1) def checkbin(self, msg, fn): self.log('Search for {0}'.format(msg)) @@ -87,34 +93,15 @@ class Buildozer(object): print '--- end commend failed' return ret - def run(self): - self.log('Build started') - - self.log('Check configuration tokens') - self.do_config_requirements() - - self.log('Check requirements for %s' % self.targetname) - self.target.check_requirements() - - self.log('Ensure build layout') - self.ensure_build_layout() - - self.log('Install platform') - self.target.install_platform() - - self.log('Compile platform') - self.target.compile_platform() - - self.log('Prebuild the application') - self.prebuild_application() - - self.log('Package the application') - self.target.build_package() - def do_config_requirements(self): pass def ensure_build_layout(self): + if not exists(self.specfilename): + print 'No {0} found in the current directory. Abandon.'.format( + self.specfilename) + exit(1) + specdir = dirname(self.specfilename) self.mkdir(join(specdir, '.buildozer', self.targetname)) self.mkdir(join(specdir, '.buildozer', self.targetname, 'platform')) @@ -158,6 +145,11 @@ class Buildozer(object): raise Exception('Unhandled extraction for type {0}'.format(archive)) + def clean_platform(self): + if not exists(self.platform_dir): + return + rmtree(self.platform_dir) + def download(self, url, filename, cwd=None): def report_hook(index, blksize, size): if size <= 0: @@ -212,7 +204,7 @@ class Buildozer(object): raise Exception('Missing version or version.regex + version.filename') - def prebuild_application(self): + def build_application(self): source_dir = realpath(self.config.getdefault('app', 'source.dir', '.')) include_exts = self.config.getlist('app', 'source.include_exts', '') exclude_exts = self.config.getlist('app', 'source.exclude_exts', '') @@ -266,15 +258,142 @@ class Buildozer(object): return realpath(join( dirname(self.specfilename), 'bin')) + # + # command line invocation + # + + def targets(self): + for fn in listdir(join(dirname(__file__), 'targets')): + if fn.startswith('.') or fn.startswith('__'): + continue + if not fn.endswith('.py'): + continue + target = fn[:-3] + try: + m = __import__('buildozer.targets.%s' % target, fromlist=['buildozer']) + yield target, m + except: + pass + + def usage(self): + print 'Usage: buildozer [target] [command1] [command2]' + print + print 'Available targets:' + for target, m in self.targets(): + print ' ' + target + doc = m.__doc__.strip().splitlines()[0] + print ' ' + doc + + print + print 'Global commands (without target):' + cmds = [x for x in dir(self) if x.startswith('cmd_')] + for cmd in cmds: + name = cmd[4:] + meth = getattr(self, cmd) + + print ' ' + name + doc = '\n'.join([' ' + x for x in + meth.__doc__.strip().splitlines()]) + print doc + + print + print 'Target commands:' + print ' clean' + print ' Clean the target environment' + print ' update' + print ' Update the target dependencies' + print ' debug' + print ' Build the application in debug mode' + print ' release' + print ' Build the application in release mode' + print ' deploy' + print ' Deploy the application on the device' + print ' run' + print ' Run the application on the device' + print + + + def run_default(self): + self.ensure_build_layout() + if 'buildozer:defaultcommand' not in self.state: + print 'No default command set.' + print 'Use "buildozer setdefault "' + exit(1) + cmd = self.state['buildozer:defaultcommand'] + self.run_command(*cmd) + + def run_command(self, args): + if '-h' in args or '--help' in args: + self.usage() + exit(0) + + if not args: + self.run_default() + return + + command, args = args[0], args[1:] + cmd = 'cmd_{0}'.format(command) + + # internal commands ? + if hasattr(self, cmd): + getattr(self, cmd)(*args) + return + + # maybe it's a target? + targets = [x[0] for x in self.targets()] + if command not in targets: + print 'Unknow command/target', command + exit(1) + + self.set_target(command) + self.target.run_commands(args) + + def prepare_for_build(self): + self.log('Preparing build') + + self.log('Ensure build layout') + self.ensure_build_layout() + + self.log('Check configuration tokens') + self.do_config_requirements() + + self.log('Check requirements for %s' % self.targetname) + self.target.check_requirements() + + self.log('Install platform') + self.target.install_platform() + + self.log('Compile platform') + self.target.compile_platform() + + def build(self): + self.log('Build the application') + self.build_application() + + self.log('Package the application') + self.target.build_package() + + def cmd_init(self, *args): + '''Create a initial buildozer.spec in the current directory + ''' + copyfile((dirname(__file__), 'default.spec'), 'buildozer.spec') + print 'File buildozer.spec created, ready to customize!' + + def cmd_clean(self, *args): + '''Clean the whole Buildozer environment. + ''' + pass + + def cmd_help(self, *args): + '''Show the Buildozer help. + ''' + self.usage() + + def cmd_setdefault(self, *args): + '''Set the default command to do when to arguments are given + ''' + self.ensure_build_layout() + self.state['buildozer:defaultcommand'] = args + def run(): - from optparse import OptionParser - - parser = OptionParser() - parser.add_option('-t', '--target', dest='target', - help='target to build (android, ios, windows, linux, osx)') - - (options, args) = parser.parse_args() - - if options.target is None: - raise Exception('Missing -t TARGET') - Buildozer('buildozer.spec', options.target).run() + Buildozer().run_command(sys.argv[1:]) diff --git a/buildozer/default.spec b/buildozer/default.spec new file mode 100644 index 0000000..51267d0 --- /dev/null +++ b/buildozer/default.spec @@ -0,0 +1,46 @@ +[app] + +# (str) Title of your application +title = My Application + +# (str) Package name +package.name = myapp + +# (str) Package domain (needed for android/ios packaging) +package.domain = org.test + +# (str) Source code where the main.py live +source.dir = . + +# (list) Source files to include (let empty to include all the files) +source.include_exts = py,png,jpg + +# (list) Source files to exclude (let empty to not excluding anything) +#source.exclude_exts = spec + +# (str) Application versionning (method 1) +version.regex = __version__ = '(.*)' +version.filename = %(source.dir)s/main.py + +# (str) Application versionning (method 2) +# version = 1.2.0 + +# (list) Application requirements +requirements = twisted,kivy + +# +# Android specific +# + +# (list) Permissions +#android.permissions = INTERNET + +# (int) Minimum SDK allowed for installation +#android.minsdk = 8 + +# (int) Android SDK to use +#android.sdk = 16 + +# (str) Android entry point, default is ok for Kivy-based app +#android.entrypoint = org.renpy.android.PythonActivity + diff --git a/buildozer/target.py b/buildozer/target.py index 7d0fcd6..0296c85 100644 --- a/buildozer/target.py +++ b/buildozer/target.py @@ -1,12 +1,69 @@ +from sys import exit class Target(object): def __init__(self, buildozer): super(Target, self).__init__() self.buildozer = buildozer + self.build_mode = 'debug' + self.platform_update = False def check_requirements(self): pass def compile_platform(self): pass + + def run_commands(self, args): + if not args: + print 'ERROR: missing target command' + self.buildozer.usage() + exit(1) + + result = [] + last_command = [] + for arg in args: + if not arg.startswith('--'): + if last_command: + result.append(last_command) + last_command = [] + last_command.append(arg) + else: + if not last_command: + print 'ERROR: argument passed without a command' + self.buildozer.usage() + exit(1) + last_command.append(arg) + if last_command: + result.append(last_command) + + for item in result: + command, args = item[0], item[1:] + if not hasattr(self, 'cmd_{0}'.format(command)): + print 'Unknown command {0}'.format(command) + exit(1) + getattr(self, 'cmd_{0}'.format(command))(args) + + def cmd_clean(self, *args): + self.buildozer.clean_platform() + + def cmd_update(self, *args): + self.platform_update = True + self.buildozer.prepare_for_build() + + def cmd_debug(self, *args): + self.buildozer.prepare_for_build() + self.build_mode = 'debug' + self.buildozer.build() + + def cmd_release(self, *args): + self.buildozer.prepare_for_build() + self.build_mode = 'release' + self.buildozer.build() + + def cmd_deploy(self, *args): + self.buildozer.prepare_for_build() + + def cmd_run(self, *args): + self.buildozer.prepare_for_build() + diff --git a/buildozer/targets/android.py b/buildozer/targets/android.py index 32ec418..64f17e8 100644 --- a/buildozer/targets/android.py +++ b/buildozer/targets/android.py @@ -1,3 +1,6 @@ +''' +Android target, based on python-for-android project +''' # # Android target # Thanks for Renpy (again) for its install_sdk.py and plat.py in the PGS4A @@ -172,12 +175,9 @@ class TargetAndroid(Target): if not self.buildozer.file_exists(pa_dir): cmd('git clone git://github.com/kivy/python-for-android', cwd=self.buildozer.platform_dir) - ''' - # don't update the latest clone, except if we asked for it. - else: + elif self.platform_update: cmd('git clean -dxf', cwd=pa_dir) cmd('git pull origin master', cwd=pa_dir) - ''' self._install_apache_ant() self.sdk_dir = sdk_dir = self._install_android_sdk() @@ -235,13 +235,18 @@ class TargetAndroid(Target): self.buildozer.state['android.requirements'] = android_requirements self.buildozer.state.sync() - def build_package(self): - dist_dir = join(self.pa_dir, 'dist', 'default') + def _get_package(self): config = self.buildozer.config package_domain = config.getdefault('app', 'package.domain', '') package = config.get('app', 'package.name') if package_domain: package = package_domain + '.' + package + return package + + def build_package(self): + dist_dir = join(self.pa_dir, 'dist', 'default') + config = self.buildozer.config + package = self._get_package() version = self.buildozer.get_version() build_cmd = ( @@ -268,15 +273,20 @@ class TargetAndroid(Target): build_cmd += ' --permission {0}'.format(permission) # build only in debug right now. - build_cmd += ' debug' + if self.build_mode == 'debug': + build_cmd += ' debug' + mode = 'debug' + else: + build_cmd += ' release' + mode = 'release-unsigned' self.buildozer.cmd(build_cmd, cwd=dist_dir) # 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}-debug.apk'.format( - title=apktitle, version=version) + apk = '{title}-{version}-{mode}.apk'.format( + title=apktitle, version=version, mode=mode) # copy to our place copyfile(join(dist_dir, 'bin', apk), @@ -284,6 +294,49 @@ class TargetAndroid(Target): self.buildozer.log('Android packaging done!') self.buildozer.log('APK {0} available in the bin directory'.format(apk)) + self.buildozer.state['android:latestapk'] = apk + self.buildozer.state['android:latestmode'] = self.build_mode + + def cmd_deploy(self, *args): + 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.') + + if state.get('android:latestmode', '') != 'debug': + self.buildozer.error( + 'Only debug APK are supported for deploy') + + # search the APK in the bin dir + apk = state['android:latestapk'] + full_apk = join(self.buildozer.bin_dir, apk) + if not self.buildozer.file_exists(full_apk): + self.buildozer.error( + 'Unable to found the latest APK. Please run "debug" again.') + + # push on the device + self.buildozer.cmd('{0} install -r {1}'.format( + self.adb_cmd, full_apk), cwd=self.buildozer.platform_dir) + + self.buildozer.log('Application pushed on the device.') + + def cmd_run(self, *args): + super(TargetAndroid, self).cmd_run(*args) + + entrypoint = self.buildozer.config.getdefault( + 'app', 'android.entrypoint', 'org.renpy.android.PythonActivity') + package = self._get_package() + + self.buildozer.cmd( + '{adb} shell am start -n {package}/{entry} -a {entry}'.format( + adb=self.adb_cmd, package=package, entry=entrypoint), + cwd=self.buildozer.platform_dir) + + self.buildozer.log('Application started on the device.') + + + def get_target(buildozer): diff --git a/buildozer/targets/ios.py b/buildozer/targets/ios.py index c8fb735..4d74006 100644 --- a/buildozer/targets/ios.py +++ b/buildozer/targets/ios.py @@ -1,3 +1,6 @@ +''' +iOS target, based on kivy-ios project. (not working yet.) +''' from buildozer.target import Target class TargetIos(Target): From e6f1db2b4b8f11cb91af4d101ee85bbcd7581e67 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Wed, 19 Dec 2012 17:40:52 +0100 Subject: [PATCH 07/32] remove unused usage --- README.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/README.rst b/README.rst index eb07114..b3ab16a 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,6 @@ Usage example ------------- #. Add buildozer repo into your PYTHONPATH. -#. Create a .spec #. Go into your application directory and do:: buildozer.py init From 6224f748be8901a272d311a1d57f897ff5e32b0d Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Wed, 19 Dec 2012 17:53:58 +0100 Subject: [PATCH 08/32] add missing files, and publish a first version --- .gitignore | 1 + COPYING | 7 +++++++ MANIFEST.in | 2 ++ README.rst | 32 ++++++++++++++++++++++---------- setup.py | 15 +++++++++++++++ buildozer.py => tools/buildozer | 2 +- 6 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 COPYING create mode 100644 MANIFEST.in create mode 100644 setup.py rename buildozer.py => tools/buildozer (73%) diff --git a/.gitignore b/.gitignore index f24cd99..158f5f1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.py[co] # Packages +.*.swp *.egg *.egg-info dist diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..75897ba --- /dev/null +++ b/COPYING @@ -0,0 +1,7 @@ +Copyright (c) 2012 Mathieu Virbel + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..973277e --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include *COPYING +recursive-include buildozer *.spec diff --git a/README.rst b/README.rst index b3ab16a..4b04154 100644 --- a/README.rst +++ b/README.rst @@ -12,29 +12,41 @@ spec for create package for Android, iOS, Windows, OSX and Linux. Usage example ------------- -#. Add buildozer repo into your PYTHONPATH. +#. Install buildozer:: + + # latest dev + git clone git://github.com/kivy/buildozer + cd buildozer + sudo python2.7 setup.py install + + # via pip (latest stable) + sudo pip install buildozer + + # via easy_install + sudo easy_install buildozer + #. Go into your application directory and do:: - buildozer.py init + buildozer init # edit the buildozer.spec, then - buildozer.py android build + buildozer android build Example of commands:: # buildozer commands - buildozer.py clean + buildozer clean # buildozer target command - buildozer.py android update - buildozer.py android install - buildozer.py android debug - buildozer.py android release + buildozer android update + buildozer android install + buildozer android debug + buildozer android release # or all in one (compile in debug, install on device) - buildozer.py android debug install + buildozer android debug install # set the default command if nothing set - buildozer.py setdefault android debug install + buildozer setdefault android debug install Usage diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ea272bf --- /dev/null +++ b/setup.py @@ -0,0 +1,15 @@ +from distutils.core import setup + +setup( + name='buildozer', + version='0.1', + author='Mathieu Virbel', + author_email='mat@kivy.org', + url='http://github.com/kivy/buildozer', + license='MIT', + packages=[ + 'buildozer', + 'buildozer.targets'], + scripts=['tools/buildozer'], + description='Generic Python packager for Android / iOS and Desktop' +) diff --git a/buildozer.py b/tools/buildozer similarity index 73% rename from buildozer.py rename to tools/buildozer index f052341..678521e 100755 --- a/buildozer.py +++ b/tools/buildozer @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2.7 if __name__ == '__main__': from buildozer import run From aea63eafe412c2863ec822ff705738727245d3ea Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Wed, 19 Dec 2012 18:13:12 +0100 Subject: [PATCH 09/32] fix buildozer init --- README.rst | 2 +- buildozer/__init__.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 4b04154..f19832c 100644 --- a/README.rst +++ b/README.rst @@ -46,7 +46,7 @@ Example of commands:: buildozer android debug install # set the default command if nothing set - buildozer setdefault android debug install + buildozer setdefault android debug install run Usage diff --git a/buildozer/__init__.py b/buildozer/__init__.py index 67ecb7e..d6d0fe8 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -376,7 +376,10 @@ class Buildozer(object): def cmd_init(self, *args): '''Create a initial buildozer.spec in the current directory ''' - copyfile((dirname(__file__), 'default.spec'), 'buildozer.spec') + if exists('buildozer.spec'): + print 'ERROR: You already have a buildozer.spec file.' + exit(1) + copyfile(join(dirname(__file__), 'default.spec'), 'buildozer.spec') print 'File buildozer.spec created, ready to customize!' def cmd_clean(self, *args): From 5a9cfb1b2e8a61a0712aa4bc9ec50fb71e76a972 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Wed, 19 Dec 2012 18:23:51 +0100 Subject: [PATCH 10/32] include the default.spec when using setup.py install --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index ea272bf..766cc18 100644 --- a/setup.py +++ b/setup.py @@ -10,6 +10,7 @@ setup( packages=[ 'buildozer', 'buildozer.targets'], + package_data={'buildozer': ['default.spec']}, scripts=['tools/buildozer'], description='Generic Python packager for Android / iOS and Desktop' ) From 1ba027e85d22bc9843c51a260cdc0180b843fe10 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Wed, 19 Dec 2012 18:27:28 +0100 Subject: [PATCH 11/32] avoid multiple execution of build() and prepare_for_build() method --- buildozer/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/buildozer/__init__.py b/buildozer/__init__.py index d6d0fe8..2e34b09 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -349,6 +349,9 @@ class Buildozer(object): self.target.run_commands(args) def prepare_for_build(self): + if hasattr(self.target, '_build_prepared'): + return + self.log('Preparing build') self.log('Ensure build layout') @@ -366,13 +369,22 @@ class Buildozer(object): self.log('Compile platform') self.target.compile_platform() + # flag to prevent multiple build + self.target._build_prepared = True + def build(self): + if hasattr(self.target, '_build_done'): + return + self.log('Build the application') self.build_application() self.log('Package the application') self.target.build_package() + # flag to prevent multiple build + self.target._build_done = True + def cmd_init(self, *args): '''Create a initial buildozer.spec in the current directory ''' From e2558d6000e756afb94bba3e1fd6fad82bc43182 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Wed, 19 Dec 2012 18:37:02 +0100 Subject: [PATCH 12/32] fix doc --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index f19832c..48e99cb 100644 --- a/README.rst +++ b/README.rst @@ -29,7 +29,7 @@ Usage example buildozer init # edit the buildozer.spec, then - buildozer android build + buildozer android debug install run Example of commands:: From bf61eb0e6b98becfd5ff344abc94b260bf35dc0e Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Wed, 19 Dec 2012 18:40:02 +0100 Subject: [PATCH 13/32] add missing .buildozer creation0 --- buildozer/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/buildozer/__init__.py b/buildozer/__init__.py index 2e34b09..3dd6b07 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -103,6 +103,7 @@ class Buildozer(object): exit(1) specdir = dirname(self.specfilename) + self.mkdir(join(specdir, '.buildozer')) self.mkdir(join(specdir, '.buildozer', self.targetname)) self.mkdir(join(specdir, '.buildozer', self.targetname, 'platform')) self.mkdir(join(specdir, '.buildozer', self.targetname, 'app')) From e30ef6364e876805319c508f0715c164342700d2 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Wed, 19 Dec 2012 19:36:00 +0100 Subject: [PATCH 14/32] seperate the state.db from the platform dir. avoid to create platform dir until we know the target. --- buildozer/__init__.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/buildozer/__init__.py b/buildozer/__init__.py index 3dd6b07..a56c7bc 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -45,6 +45,7 @@ class Buildozer(object): self.targetname = target m = __import__('buildozer.targets.%s' % target, fromlist=['buildozer']) self.target = m.get_target(self) + self.ensure_build_layout() def _get_config_list(self, section, token, default=None): values = self.config.getdefault(section, token, default).split(',') @@ -104,11 +105,13 @@ class Buildozer(object): specdir = dirname(self.specfilename) self.mkdir(join(specdir, '.buildozer')) - self.mkdir(join(specdir, '.buildozer', self.targetname)) - self.mkdir(join(specdir, '.buildozer', self.targetname, 'platform')) - self.mkdir(join(specdir, '.buildozer', self.targetname, 'app')) self.mkdir(join(specdir, 'bin')) - self.state = shelve.open(join(self.platform_dir, 'state.db')) + self.state = shelve.open(join(self.buildozer_dir, 'state.db')) + + if self.targetname: + self.mkdir(join(specdir, '.buildozer', self.targetname)) + self.mkdir(join(specdir, '.buildozer', self.targetname, 'platform')) + self.mkdir(join(specdir, '.buildozer', self.targetname, 'app')) def mkdir(self, dn): if exists(dn): @@ -254,6 +257,11 @@ class Buildozer(object): dirname(self.specfilename), '.buildozer', self.targetname, 'app')) + @property + def buildozer_dir(self): + return realpath(join( + dirname(self.specfilename), '.buildozer')) + @property def bin_dir(self): return realpath(join( From 2e250079dc1c6535f469fa91737e425553f3c971 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Wed, 19 Dec 2012 19:37:04 +0100 Subject: [PATCH 15/32] fix default command --- buildozer/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildozer/__init__.py b/buildozer/__init__.py index a56c7bc..fdd7838 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -329,7 +329,7 @@ class Buildozer(object): print 'Use "buildozer setdefault "' exit(1) cmd = self.state['buildozer:defaultcommand'] - self.run_command(*cmd) + self.run_command(cmd) def run_command(self, args): if '-h' in args or '--help' in args: From c25c2dc9e975382ff1b98b0677d0c9a8ea794182 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 20 Dec 2012 00:40:41 +0100 Subject: [PATCH 16/32] add seperation between "global" and "local" stuff, and allow to use custom ndk/sdk/ant directory --- README.rst | 49 +------------ buildozer/__init__.py | 50 ++++++++----- buildozer/default.spec | 20 ++++-- buildozer/targets/android.py | 136 ++++++++++++++++++++++++----------- 4 files changed, 145 insertions(+), 110 deletions(-) diff --git a/README.rst b/README.rst index 48e99cb..4e2b015 100644 --- a/README.rst +++ b/README.rst @@ -93,51 +93,6 @@ buildozer.spec See buildozer/default.spec for an up-to-date spec file. -:: - - [app] - - # (str) Title of your application - title = My Application - - # (str) Package name - package.name = myapp - - # (str) Package domain (needed for android/ios packaging) - package.domain = org.test - - # (str) Source code where the main.py live - source.dir = . - - # (list) Source files to include (let empty to include all the files) - source.include_exts = py,png,jpg - - # (list) Source files to exclude (let empty to not excluding anything) - #source.exclude_exts = spec - - # (str) Application versionning (method 1) - version.regex = __version__ = '(.*)' - version.filename = %(source.dir)s/main.py - - # (str) Application versionning (method 2) - # version = 1.2.0 - - # (list) Application requirements - requirements = twisted,kivy - - # - # Android specific - # - - # (list) Permissions - #android.permissions = INTERNET - - # (int) Minimum SDK allowed for installation - #android.minsdk = 8 - - # (int) Android SDK to use - #android.sdk = 16 - - # (str) Android entry point, default is ok for Kivy-based app - #android.entrypoint = org.renpy.android.PythonActivity +.. include:: buildozer/default.spec + :code: ini diff --git a/buildozer/__init__.py b/buildozer/__init__.py index fdd7838..75b0cc8 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -17,9 +17,9 @@ from sys import stdout, exit from urllib import urlretrieve from re import search from ConfigParser import SafeConfigParser -from os.path import join, exists, dirname, realpath, splitext +from os.path import join, exists, dirname, realpath, splitext, expanduser from subprocess import Popen, PIPE -from os import environ, mkdir, unlink, rename, walk, sep, listdir +from os import environ, unlink, rename, walk, sep, listdir, makedirs from copy import copy from shutil import copyfile, rmtree @@ -103,20 +103,25 @@ class Buildozer(object): self.specfilename) exit(1) + # create global dir + self.mkdir(self.global_buildozer_dir) + + # create local dir specdir = dirname(self.specfilename) self.mkdir(join(specdir, '.buildozer')) self.mkdir(join(specdir, 'bin')) self.state = shelve.open(join(self.buildozer_dir, 'state.db')) if self.targetname: - self.mkdir(join(specdir, '.buildozer', self.targetname)) - self.mkdir(join(specdir, '.buildozer', self.targetname, 'platform')) - self.mkdir(join(specdir, '.buildozer', self.targetname, 'app')) + target = self.targetname + self.mkdir(join(self.global_platform_dir, target, 'platform')) + self.mkdir(join(specdir, '.buildozer', target, 'platform')) + self.mkdir(join(specdir, '.buildozer', target, 'app')) def mkdir(self, dn): if exists(dn): return - mkdir(dn) + makedirs(dn) def file_exists(self, *args): return exists(join(*args)) @@ -125,6 +130,7 @@ class Buildozer(object): if cwd: source = join(cwd, source) target = join(cwd, target) + self.log('Rename {0} to {1}'.format(source, target)) rename(source, target) def file_extract(self, archive, cwd=None): @@ -245,28 +251,34 @@ class Buildozer(object): self.log('Copy {0}'.format(sfn)) copyfile(sfn, rfn) - @property - def platform_dir(self): - return realpath( - join(dirname(self.specfilename), '.buildozer', - self.targetname, 'platform')) - - @property - def app_dir(self): - return realpath(join( - dirname(self.specfilename), '.buildozer', - self.targetname, 'app')) - @property def buildozer_dir(self): return realpath(join( dirname(self.specfilename), '.buildozer')) + @property + def platform_dir(self): + return join(self.buildozer_dir, self.targetname, 'platform') + + @property + def app_dir(self): + return join(self.buildozer_dir, self.targetname, 'app') + @property def bin_dir(self): return realpath(join( dirname(self.specfilename), 'bin')) + @property + def global_buildozer_dir(self): + return join(expanduser('~'), '.buildozer') + + @property + def global_platform_dir(self): + return join(self.global_buildozer_dir, self.targetname, 'platform') + + + # # command line invocation # @@ -419,5 +431,7 @@ class Buildozer(object): self.ensure_build_layout() self.state['buildozer:defaultcommand'] = args + def run(): Buildozer().run_command(sys.argv[1:]) + diff --git a/buildozer/default.spec b/buildozer/default.spec index 51267d0..9b3a117 100644 --- a/buildozer/default.spec +++ b/buildozer/default.spec @@ -35,11 +35,23 @@ requirements = twisted,kivy # (list) Permissions #android.permissions = INTERNET -# (int) Minimum SDK allowed for installation -#android.minsdk = 8 +# (int) Android API to use +#android.api = 14 -# (int) Android SDK to use -#android.sdk = 16 +# (int) Minimum API required (8 = Android 2.2 devices) +#android.minapi = 8 + +# (int) Android SDK version to use +#android.sdk = 21 + +# (str) Android NDK version to use +#android.ndk = 8c + +# (str) Android NDK directory (if empty, it will be automatically downloaded.) +#android.ndk_path = + +# (str) Android SDK directory (if empty, it will be automatically downloaded.) +#android.sdk_path = # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity diff --git a/buildozer/targets/android.py b/buildozer/targets/android.py index 64f17e8..ae95327 100644 --- a/buildozer/targets/android.py +++ b/buildozer/targets/android.py @@ -9,6 +9,7 @@ Android target, based on python-for-android project ANDROID_API = '14' +ANDROID_MINAPI = '8' ANDROID_SDK_VERSION = '21' ANDROID_NDK_VERSION = '8c' APACHE_ANT_VERSION = '1.8.4' @@ -24,27 +25,80 @@ from shutil import copyfile class TargetAndroid(Target): + @property + def android_sdk_version(self): + 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) + + @property + def android_api(self): + 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) + + @property + def android_sdk_dir(self): + directory = self.buildozer.config.getdefault( + 'app', 'android.sdk_path', '') + if directory: + return realpath(directory) + version = self.buildozer.config.getdefault( + 'app', 'android.sdk', self.android_sdk_version) + return join(self.buildozer.global_platform_dir, + 'android-sdk-{0}'.format(version)) + + @property + def android_ndk_dir(self): + directory = self.buildozer.config.getdefault( + 'app', 'android.ndk_path', '') + if directory: + return realpath(directory) + version = self.buildozer.config.getdefault( + 'app', 'android.ndk', self.android_ndk_version) + return join(self.buildozer.global_platform_dir, + 'android-sdk-{0}'.format(version)) + + @property + def apache_ant_dir(self): + directory = self.buildozer.config.getdefault( + 'app', 'android.ant_path', '') + if directory: + return realpath(directory) + version = self.buildozer.config.getdefault( + 'app', 'android.ant', APACHE_ANT_VERSION) + return join(self.buildozer.global_platform_dir, + 'apache-ant-{0}'.format(version)) + def check_requirements(self): if platform in ('win32', 'cygwin'): try: self._set_win32_java_home() except: traceback.print_exc() - self.android_cmd = join('android-sdk', 'tools', 'android.bat') - self.ant_cmd = join('apache-ant', 'bin', 'ant.bat') - self.adb_cmd = join('android-sdk', 'platform-tools', 'adb.exe') + self.android_cmd = join(self.android_sdk_dir, 'tools', 'android.bat') + self.ant_cmd = join(self.apache_ant_dir, 'bin', 'ant.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', ): - self.android_cmd = join('android-sdk', 'tools', 'android') - self.ant_cmd = join('apache-ant', 'bin', 'ant') - self.adb_cmd = join('android-sdk', 'platform-tools', 'adb') + self.android_cmd = join(self.android_sdk_dir, 'tools', 'android') + self.ant_cmd = join(self.apache_ant_dir, 'bin', 'ant') + self.adb_cmd = join(self.android_sdk_dir, 'platform-tools', 'adb') self.javac_cmd = self._locate_java('javac') self.keytool_cmd = self._locate_java('keytool') else: - self.android_cmd = join('android-sdk', 'tools', 'android') - self.ant_cmd = join('apache-ant', 'bin', 'ant') - self.adb_cmd = join('android-sdk', 'platform-tools', 'adb') + self.android_cmd = join(self.android_sdk_dir, 'tools', 'android') + self.ant_cmd = join(self.apache_ant_dir, 'bin', 'ant') + self.adb_cmd = join(self.android_sdk_dir, 'platform-tools', 'adb') self.javac_cmd = self._locate_java('javac') self.keytool_cmd = self._locate_java('keytool') @@ -74,26 +128,23 @@ class TargetAndroid(Target): return s def _install_apache_ant(self): - ant_dir = join(self.buildozer.platform_dir, 'apache-ant') + ant_dir = self.apache_ant_dir if self.buildozer.file_exists(ant_dir): self.buildozer.log('Apache ANT found at {0}'.format(ant_dir)) return ant_dir self.buildozer.log('Android ANT is missing, downloading') archive = 'apache-ant-{0}-bin.tar.gz'.format(APACHE_ANT_VERSION) - unpacked = 'apache-ant-{0}'.format(APACHE_ANT_VERSION) url = 'http://archive.apache.org/dist/ant/binaries/' self.buildozer.download(url, archive, - cwd=self.buildozer.platform_dir) - - self.buildozer.file_extract(archive, cwd=self.buildozer.platform_dir) - self.buildozer.file_rename(unpacked, 'apache-ant', - cwd=self.buildozer.platform_dir) + cwd=self.buildozer.global_platform_dir) + self.buildozer.file_extract(archive, + cwd=self.buildozer.global_platform_dir) self.buildozer.log('Apache ANT installation done.') return ant_dir def _install_android_sdk(self): - sdk_dir = join(self.buildozer.platform_dir, 'android-sdk') + sdk_dir = self.android_sdk_dir if self.buildozer.file_exists(sdk_dir): self.buildozer.log('Android SDK found at {0}'.format(sdk_dir)) return sdk_dir @@ -111,20 +162,21 @@ class TargetAndroid(Target): else: raise SystemError('Unsupported platform: {0}'.format(platform)) - archive = archive.format(ANDROID_SDK_VERSION) + archive = archive.format(self.android_sdk_version) url = 'http://dl.google.com/android/' self.buildozer.download(url, archive, - cwd=self.buildozer.platform_dir) + cwd=self.buildozer.global_platform_dir) self.buildozer.log('Unpacking Android SDK') - self.buildozer.file_extract(archive, cwd=self.buildozer.platform_dir) - self.buildozer.file_rename(unpacked, 'android-sdk', - cwd=self.buildozer.platform_dir) + self.buildozer.file_extract(archive, + cwd=self.buildozer.global_platform_dir) + self.buildozer.file_rename(unpacked, sdk_dir, + cwd=self.buildozer.global_platform_dir) self.buildozer.log('Android SDK installation done.') return sdk_dir def _install_android_ndk(self): - ndk_dir = join(self.buildozer.platform_dir, 'android-ndk') + ndk_dir = self.android_ndk_dir if self.buildozer.file_exists(ndk_dir): self.buildozer.log('Android NDK found at {0}'.format(ndk_dir)) return ndk_dir @@ -140,33 +192,34 @@ class TargetAndroid(Target): raise SystemError('Unsupported platform: {0}'.format(platform)) unpacked = 'android-ndk-r{0}' - archive = archive.format(ANDROID_NDK_VERSION) - unpacked = unpacked.format(ANDROID_NDK_VERSION) + archive = archive.format(self.android_ndk_version) + unpacked = unpacked.format(self.android_ndk_version) url = 'http://dl.google.com/android/ndk/' self.buildozer.download(url, archive, - cwd=self.buildozer.platform_dir) + cwd=self.buildozer.global_platform_dir) self.buildozer.log('Unpacking Android NDK') - self.buildozer.file_extract(archive, cwd=self.buildozer.platform_dir) - self.buildozer.file_rename(unpacked, 'android-ndk', - cwd=self.buildozer.platform_dir) + self.buildozer.file_extract(archive, + cwd=self.buildozer.global_platform_dir) + self.buildozer.file_rename(unpacked, ndk_dir, + cwd=self.buildozer.global_platform_dir) self.buildozer.log('Android NDK installation done.') return ndk_dir def _install_android_packages(self): packages = [] - android_platform = join(self.sdk_dir, 'platforms', - 'android-{0}'.format(ANDROID_API)) + android_platform = join(self.android_sdk_dir, 'platforms', + 'android-{0}'.format(self.android_api)) if not self.buildozer.file_exists(android_platform): - packages.append('android-{0}'.format(ANDROID_API)) - if not self.buildozer.file_exists(self.sdk_dir, 'platform-tools'): + packages.append('android-{0}'.format(self.android_api)) + if not self.buildozer.file_exists(self.android_sdk_dir, 'platform-tools'): packages.append('platform-tools') if not packages: self.buildozer.log('Android packages already installed.') return self.buildozer.cmd('{0} update sdk -u -a -t {1}'.format( self.android_cmd, ','.join(packages)), - cwd=self.buildozer.platform_dir) + cwd=self.buildozer.global_platform_dir) self.buildozer.log('Android packages installation done.') def install_platform(self): @@ -180,14 +233,15 @@ class TargetAndroid(Target): cmd('git pull origin master', cwd=pa_dir) self._install_apache_ant() - self.sdk_dir = sdk_dir = self._install_android_sdk() - self.ndk_dir = ndk_dir = self._install_android_ndk() + self._install_android_sdk() + self._install_android_ndk() self._install_android_packages() + self.buildozer.environ.update({ - 'ANDROIDSDK': realpath(sdk_dir), - 'ANDROIDNDK': realpath(ndk_dir), + 'ANDROIDSDK': self.android_sdk_dir, + 'ANDROIDNDK': self.android_ndk_dir, 'ANDROIDAPI': ANDROID_API, - 'ANDROIDNDKVER': ANDROID_NDK_VERSION}) + 'ANDROIDNDKVER': self.android_ndk_version}) def compile_platform(self): # for android, the compilation depends really on the app requirements. @@ -317,7 +371,7 @@ class TargetAndroid(Target): # push on the device self.buildozer.cmd('{0} install -r {1}'.format( - self.adb_cmd, full_apk), cwd=self.buildozer.platform_dir) + self.adb_cmd, full_apk), cwd=self.buildozer.global_platform_dir) self.buildozer.log('Application pushed on the device.') @@ -331,7 +385,7 @@ class TargetAndroid(Target): self.buildozer.cmd( '{adb} shell am start -n {package}/{entry} -a {entry}'.format( adb=self.adb_cmd, package=package, entry=entrypoint), - cwd=self.buildozer.platform_dir) + cwd=self.buildozer.global_platform_dir) self.buildozer.log('Application started on the device.') From fc6be4002814dde024739b94360844ac2881a07e Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 20 Dec 2012 00:48:24 +0100 Subject: [PATCH 17/32] fix readme --- README.rst | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 4e2b015..10fc14d 100644 --- a/README.rst +++ b/README.rst @@ -91,8 +91,5 @@ Usage buildozer.spec -------------- -See buildozer/default.spec for an up-to-date spec file. - -.. include:: buildozer/default.spec - :code: ini +See `buildozer/default.spec `_ for an up-to-date spec file. From 0dd7d484c0fc0e41b5442da1b03397cebd8f7ea7 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 20 Dec 2012 01:01:19 +0100 Subject: [PATCH 18/32] update README + bump to 0.2 --- README.rst | 37 +++++++++++++------------------------ buildozer/__init__.py | 41 ++++++++++++++++++++++------------------- setup.py | 4 +++- 3 files changed, 38 insertions(+), 44 deletions(-) diff --git a/README.rst b/README.rst index 10fc14d..7fa1e2c 100644 --- a/README.rst +++ b/README.rst @@ -57,34 +57,23 @@ Usage Usage: buildozer [target] [command1] [command2] Available targets: - android - Android target, based on python-for-android project - ios - iOS target, based on kivy-ios project. (not working yet.) + android Android target, based on python-for-android project + ios iOS target, based on kivy-ios project. (not working yet.) Global commands (without target): - clean - Clean the whole Buildozer environment. - help - Show the Buildozer help. - init - Create a initial buildozer.spec in the current directory - setdefault - Set the default command to do when to arguments are given + clean Clean the whole Buildozer environment. + help Show the Buildozer help. + init Create a initial buildozer.spec in the current directory + setdefault Set the default command to do when to arguments are given + version Show the Buildozer version Target commands: - clean - Clean the target environment - update - Update the target dependencies - debug - Build the application in debug mode - release - Build the application in release mode - deploy - Install the application on the device - run - Run the application on the device + clean Clean the target environment + update Update the target dependencies + debug Build the application in debug mode + release Build the application in release mode + deploy Deploy the application on the device + run Run the application on the device diff --git a/buildozer/__init__.py b/buildozer/__init__.py index 75b0cc8..df721eb 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -10,6 +10,8 @@ Layout directory for buildozer: ''' +__version__ = '0.2' + import shelve import zipfile import sys @@ -301,9 +303,8 @@ class Buildozer(object): print print 'Available targets:' for target, m in self.targets(): - print ' ' + target - doc = m.__doc__.strip().splitlines()[0] - print ' ' + doc + doc = m.__doc__.strip().splitlines()[0].strip() + print ' {0:<18} {1}'.format(target, doc) print print 'Global commands (without target):' @@ -312,25 +313,18 @@ class Buildozer(object): name = cmd[4:] meth = getattr(self, cmd) - print ' ' + name - doc = '\n'.join([' ' + x for x in - meth.__doc__.strip().splitlines()]) - print doc + doc = [x for x in + meth.__doc__.strip().splitlines()][0].strip() + print ' {0:<18} {1}'.format(name, doc) print print 'Target commands:' - print ' clean' - print ' Clean the target environment' - print ' update' - print ' Update the target dependencies' - print ' debug' - print ' Build the application in debug mode' - print ' release' - print ' Build the application in release mode' - print ' deploy' - print ' Deploy the application on the device' - print ' run' - print ' Run the application on the device' + print ' clean Clean the target environment' + print ' update Update the target dependencies' + print ' debug Build the application in debug mode' + print ' release Build the application in release mode' + print ' deploy Deploy the application on the device' + print ' run Run the application on the device' print @@ -348,6 +342,10 @@ class Buildozer(object): self.usage() exit(0) + if '--version' in args: + print 'Buildozer {0}'.format(__version__) + exit(0) + if not args: self.run_default() return @@ -431,6 +429,11 @@ class Buildozer(object): self.ensure_build_layout() self.state['buildozer:defaultcommand'] = args + def cmd_version(self, *args): + '''Show the Buildozer version + ''' + print 'Buildozer {0}'.format(__version__) + def run(): Buildozer().run_command(sys.argv[1:]) diff --git a/setup.py b/setup.py index 766cc18..4a24df3 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,10 @@ from distutils.core import setup +import buildozer + setup( name='buildozer', - version='0.1', + version=buildozer.__version__, author='Mathieu Virbel', author_email='mat@kivy.org', url='http://github.com/kivy/buildozer', From 1a2353362c033148e32aa15086666a17f642349d Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 20 Dec 2012 01:02:12 +0100 Subject: [PATCH 19/32] bump to 0.3-dev --- buildozer/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildozer/__init__.py b/buildozer/__init__.py index df721eb..22d9419 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -10,7 +10,7 @@ Layout directory for buildozer: ''' -__version__ = '0.2' +__version__ = '0.3-dev' import shelve import zipfile From 4a708a0295378e8bad74e5f607f69b7c4b0a938d Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 20 Dec 2012 01:02:57 +0100 Subject: [PATCH 20/32] typo --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 7fa1e2c..3f346a8 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ Buildozer THIS IS A WORK IN PROGRESS, DO NOT USE. -Buildozer is a tool for creating application packages easilly. +Buildozer is a tool for creating application packages easily. The goal is to have one "buildozer.spec" file in your app directory: it describe your application requirements, titles, etc. Buildozer will use that From b348237188d02cb9e3daf54dd39cfdeb6251edcd Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 20 Dec 2012 01:04:39 +0100 Subject: [PATCH 21/32] moar typo --- README.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 3f346a8..29b745b 100644 --- a/README.rst +++ b/README.rst @@ -29,7 +29,7 @@ Usage example buildozer init # edit the buildozer.spec, then - buildozer android debug install run + buildozer android debug deploy run Example of commands:: @@ -38,15 +38,15 @@ Example of commands:: # buildozer target command buildozer android update - buildozer android install + buildozer android deploy buildozer android debug buildozer android release - # or all in one (compile in debug, install on device) - buildozer android debug install + # or all in one (compile in debug, deploy on device) + buildozer android debug deploy # set the default command if nothing set - buildozer setdefault android debug install run + buildozer setdefault android debug deploy run Usage From 2f529b4f99eba3d1525c641a5c899a0a95614f20 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 20 Dec 2012 14:44:56 +0100 Subject: [PATCH 22/32] add custom commands + usage + ability to follow an stdout command --- buildozer/__init__.py | 36 ++++++++++++++++++++++++++++++------ buildozer/target.py | 10 ++++++++++ buildozer/targets/android.py | 7 ++++++- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/buildozer/__init__.py b/buildozer/__init__.py index 22d9419..0506bd2 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -28,6 +28,8 @@ from shutil import copyfile, rmtree class Buildozer(object): + standard_cmds = ('clean', 'update', 'debug', 'release', 'deploy', 'run') + def __init__(self, filename='buildozer.spec', target=None): super(Buildozer, self).__init__() self.environ = {} @@ -85,16 +87,25 @@ class Buildozer(object): env.update(self.environ) kwargs.setdefault('env', env) self.log('run %r' % command) - c = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, **kwargs) - ret = c.communicate() - if c.returncode != 0: + process = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, **kwargs) + ret_stdout = '' + while True: + nextline = process.stdout.readline() + if nextline == '' and process.poll() != None: + break + ret_stdout += nextline + stdout.write(nextline) + stdout.flush() + + ret = process.communicate() + if process.returncode != 0: print '--- command failed' print '-- stdout output' - print ret[0] + print ret_stdout print '-- stderr output' print ret[1] print '--- end commend failed' - return ret + return (ret_stdout, ret[1]) def do_config_requirements(self): pass @@ -302,7 +313,8 @@ class Buildozer(object): print 'Usage: buildozer [target] [command1] [command2]' print print 'Available targets:' - for target, m in self.targets(): + targets = list(self.targets()) + for target, m in targets: doc = m.__doc__.strip().splitlines()[0].strip() print ' {0:<18} {1}'.format(target, doc) @@ -325,6 +337,18 @@ class Buildozer(object): print ' release Build the application in release mode' print ' deploy Deploy the application on the device' print ' run Run the application on the device' + + for target, m in targets: + mt = m.get_target(self) + commands = mt.get_custom_commands() + if not commands: + continue + print + print 'Target "{0}" commands:'.format(target) + for command, doc in commands: + doc = doc.strip().splitlines()[0].strip() + print ' {0:<18} {1}'.format(command, doc) + print diff --git a/buildozer/target.py b/buildozer/target.py index 0296c85..08e0a1f 100644 --- a/buildozer/target.py +++ b/buildozer/target.py @@ -14,6 +14,16 @@ class Target(object): def compile_platform(self): pass + def get_custom_commands(self): + result = [] + for x in dir(self): + if not x.startswith('cmd_'): + continue + if x[4:] in self.buildozer.standard_cmds: + continue + result.append((x[4:], getattr(self, x).__doc__)) + return result + def run_commands(self, args): if not args: print 'ERROR: missing target command' diff --git a/buildozer/targets/android.py b/buildozer/targets/android.py index ae95327..37cec98 100644 --- a/buildozer/targets/android.py +++ b/buildozer/targets/android.py @@ -389,7 +389,12 @@ class TargetAndroid(Target): self.buildozer.log('Application started on the device.') - + def cmd_logcat(self, *args): + '''Show the log from the device + ''' + self.check_requirements() + self.buildozer.cmd('{adb} logcat'.format(adb=self.adb_cmd), + cwd=self.buildozer.global_platform_dir) From 3e908d8d060a5934bc6f7f0b046a7c2fa3909902 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 20 Dec 2012 20:21:46 +0100 Subject: [PATCH 23/32] enhance cmd() stdout/stderr capture, and use fcntl/select to faster redirection. avoid to store stdout/stderr if not used. --- buildozer/__init__.py | 79 +++++++++++++++++++++++++++--------- buildozer/targets/android.py | 2 +- 2 files changed, 60 insertions(+), 21 deletions(-) diff --git a/buildozer/__init__.py b/buildozer/__init__.py index 0506bd2..f46641a 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -15,7 +15,10 @@ __version__ = '0.3-dev' import shelve import zipfile import sys -from sys import stdout, exit +import fcntl +import os +from select import select +from sys import stdout, stderr, exit from urllib import urlretrieve from re import search from ConfigParser import SafeConfigParser @@ -83,29 +86,65 @@ class Buildozer(object): def cmd(self, command, **kwargs): #print ' '.join(['{0}={1}'.format(*args) for args in # self.environ.iteritems()]) + + get_stdout = kwargs.pop('get_stdout', False) + get_stderr = kwargs.pop('get_stderr', False) + + # prepare the environ, based on the system + our own env env = copy(environ) env.update(self.environ) - kwargs.setdefault('env', env) - self.log('run %r' % command) - process = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, **kwargs) - ret_stdout = '' - while True: - nextline = process.stdout.readline() - if nextline == '' and process.poll() != None: - break - ret_stdout += nextline - stdout.write(nextline) - stdout.flush() - ret = process.communicate() + # prepare the process + kwargs.setdefault('env', env) + kwargs.setdefault('stdout', PIPE) + kwargs.setdefault('stderr', PIPE) + kwargs.setdefault('close_fds', True) + kwargs.setdefault('shell', True) + self.log('Run %r' % command) + + # open the process + process = Popen(command, **kwargs) + + # prepare fds + fd_stdout = process.stdout.fileno() + fd_stderr = process.stderr.fileno() + fcntl.fcntl( + fd_stdout, fcntl.F_SETFL, + fcntl.fcntl(fd_stdout, fcntl.F_GETFL) | os.O_NONBLOCK) + fcntl.fcntl( + fd_stderr, fcntl.F_SETFL, + fcntl.fcntl(fd_stderr, fcntl.F_GETFL) | os.O_NONBLOCK) + + ret_stdout = [] if get_stdout else None + ret_stderr = [] if get_stderr else None + while True: + readx = select([fd_stdout, fd_stderr], [], [])[0] + if fd_stdout in readx: + chunk = process.stdout.read() + if chunk == '': + break + if get_stdout: + ret_stdout.append(chunk) + stdout.write(chunk) + if fd_stderr in readx: + chunk = process.stderr.read() + if chunk == '': + break + if get_stderr: + ret_stderr.append(chunk) + stderr.write(chunk) + + stdout.flush() + stderr.flush() + + process.communicate() if process.returncode != 0: - print '--- command failed' - print '-- stdout output' - print ret_stdout - print '-- stderr output' - print ret[1] - print '--- end commend failed' - return (ret_stdout, ret[1]) + self.error('Command failed: {0}'.format(command)) + if ret_stdout: + ret_stdout = ''.join(ret_stdout) + if ret_stderr: + ret_stderr = ''.join(ret_stderr) + return (ret_stdout, ret_stderr) def do_config_requirements(self): pass diff --git a/buildozer/targets/android.py b/buildozer/targets/android.py index 37cec98..a7dd0ba 100644 --- a/buildozer/targets/android.py +++ b/buildozer/targets/android.py @@ -253,7 +253,7 @@ class TargetAndroid(Target): # we need to extract the requirements that python-for-android knows # about available_modules = self.buildozer.cmd( - './distribute.sh -l', cwd=self.pa_dir)[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') available_modules = available_modules[19:].splitlines()[0].split() From bc279d1839f56ee451564e6a275969c9bd0234cb Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Fri, 21 Dec 2012 01:38:37 +0100 Subject: [PATCH 24/32] add color in the log! --- buildozer/__init__.py | 171 ++++++++++++++++++++++------------- buildozer/targets/android.py | 38 ++++---- 2 files changed, 127 insertions(+), 82 deletions(-) diff --git a/buildozer/__init__.py b/buildozer/__init__.py index f46641a..c8a7052 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -28,6 +28,14 @@ from os import environ, unlink, rename, walk, sep, listdir, makedirs from copy import copy from shutil import copyfile, rmtree +RESET_SEQ = "\033[0m" +COLOR_SEQ = "\033[1;{0}m" +BOLD_SEQ = "\033[1m" +BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) +USE_COLOR = 'NO_COLOR' not in environ +# error, info, debug +LOG_LEVELS_C = (RED, BLUE, BLACK) +LOG_LEVELS_T = 'EID' class Buildozer(object): @@ -43,43 +51,89 @@ class Buildozer(object): self.config.getdefault = self._get_config_default self.config.read(filename) + self.check_configuration_tokens() + self.targetname = None self.target = None if target: self.set_target(target) def set_target(self, target): + '''Set the target to use (one of buildozer.targets, such as "android") + ''' self.targetname = target m = __import__('buildozer.targets.%s' % target, fromlist=['buildozer']) self.target = m.get_target(self) - self.ensure_build_layout() + self.check_build_layout() - def _get_config_list(self, section, token, default=None): - values = self.config.getdefault(section, token, default).split(',') - return [x.strip() for x in values] + def prepare_for_build(self): + '''Prepare the build. + ''' + assert(self.target is not None) + if hasattr(self.target, '_build_prepared'): + return - def _get_config_default(self, section, token, default=None): - if not self.config.has_section(section): - return default - if not self.config.has_option(section, token): - return default - return self.config.get(section, token) + self.info('Preparing build') - def log(self, msg): - print '-', msg + self.info('Check requirements for %s' % self.targetname) + self.target.check_requirements() + + self.info('Install platform') + self.target.install_platform() + + self.info('Compile platform') + self.target.compile_platform() + + # flag to prevent multiple build + self.target._build_prepared = True + + def build(self): + '''Do the build. + + The target can set build_mode to 'release' or 'debug' before calling + this method. + + (:meth:`prepare_for_build` must have been call before.) + ''' + assert(self.target is not None) + assert(hasattr(self.target, '_build_prepared')) + + if hasattr(self.target, '_build_done'): + return + + self.info('Build the application') + self.build_application() + + self.info('Package the application') + self.target.build_package() + + # flag to prevent multiple build + self.target._build_done = True + + def log(self, level, msg): + if USE_COLOR: + color = COLOR_SEQ.format(30 + LOG_LEVELS_C[level]) + print ''.join((RESET_SEQ, color, '# ', msg, RESET_SEQ)) + else: + print LOG_LEVELS_T[level], msg + + def debug(self, msg): + self.log(2, msg) + + def info(self, msg): + self.log(1, msg) def error(self, msg): - print 'E', msg - exit(1) + self.log(0, msg) def checkbin(self, msg, fn): - self.log('Search for {0}'.format(msg)) + self.debug('Search for {0}'.format(msg)) if exists(fn): return realpath(fn) for dn in environ['PATH'].split(':'): rfn = realpath(join(dn, fn)) if exists(rfn): - self.log(' -> found at {0}'.format(rfn)) + self.debug(' -> found at {0}'.format(rfn)) return rfn raise Exception(msg + 'not found') @@ -100,7 +154,7 @@ class Buildozer(object): kwargs.setdefault('stderr', PIPE) kwargs.setdefault('close_fds', True) kwargs.setdefault('shell', True) - self.log('Run %r' % command) + self.debug('Run %r' % command) # open the process process = Popen(command, **kwargs) @@ -146,10 +200,17 @@ class Buildozer(object): ret_stderr = ''.join(ret_stderr) return (ret_stdout, ret_stderr) - def do_config_requirements(self): - pass + def check_configuration_tokens(self): + '''Ensure the spec file is 'correct'. + ''' + self.info('Check configuration tokens') + + def check_build_layout(self): + '''Ensure the build (local and global) directory layout and files are + ready. + ''' + self.info('Ensure build layout') - def ensure_build_layout(self): if not exists(self.specfilename): print 'No {0} found in the current directory. Abandon.'.format( self.specfilename) @@ -173,6 +234,7 @@ class Buildozer(object): def mkdir(self, dn): if exists(dn): return + self.debug('Create directory {0}', dn) makedirs(dn) def file_exists(self, *args): @@ -182,7 +244,7 @@ class Buildozer(object): if cwd: source = join(cwd, source) target = join(cwd, target) - self.log('Rename {0} to {1}'.format(source, target)) + self.debug('Rename {0} to {1}'.format(source, target)) rename(source, target) def file_extract(self, archive, cwd=None): @@ -208,6 +270,7 @@ class Buildozer(object): raise Exception('Unhandled extraction for type {0}'.format(archive)) def clean_platform(self): + self.info('Clean the platform build directory') if not exists(self.platform_dir): return rmtree(self.platform_dir) @@ -228,7 +291,7 @@ class Buildozer(object): if self.file_exists(filename): unlink(filename) - self.log('Downloading {0}'.format(url)) + self.debug('Downloading {0}'.format(url)) urlretrieve(url, filename, report_hook) return filename @@ -261,7 +324,7 @@ class Buildozer(object): raise Exception( 'Unable to found capture version in %r' % fn) version = match.groups()[0] - self.log('Captured version: {0}'.format(version)) + self.debug('Captured version: {0}'.format(version)) return version raise Exception('Missing version or version.regex + version.filename') @@ -300,7 +363,7 @@ class Buildozer(object): self.mkdir(dfn) # copy! - self.log('Copy {0}'.format(sfn)) + self.debug('Copy {0}'.format(sfn)) copyfile(sfn, rfn) @property @@ -392,7 +455,7 @@ class Buildozer(object): def run_default(self): - self.ensure_build_layout() + self.check_build_layout() if 'buildozer:defaultcommand' not in self.state: print 'No default command set.' print 'Use "buildozer setdefault "' @@ -430,43 +493,6 @@ class Buildozer(object): self.set_target(command) self.target.run_commands(args) - def prepare_for_build(self): - if hasattr(self.target, '_build_prepared'): - return - - self.log('Preparing build') - - self.log('Ensure build layout') - self.ensure_build_layout() - - self.log('Check configuration tokens') - self.do_config_requirements() - - self.log('Check requirements for %s' % self.targetname) - self.target.check_requirements() - - self.log('Install platform') - self.target.install_platform() - - self.log('Compile platform') - self.target.compile_platform() - - # flag to prevent multiple build - self.target._build_prepared = True - - def build(self): - if hasattr(self.target, '_build_done'): - return - - self.log('Build the application') - self.build_application() - - self.log('Package the application') - self.target.build_package() - - # flag to prevent multiple build - self.target._build_done = True - def cmd_init(self, *args): '''Create a initial buildozer.spec in the current directory ''' @@ -489,7 +515,7 @@ class Buildozer(object): def cmd_setdefault(self, *args): '''Set the default command to do when to arguments are given ''' - self.ensure_build_layout() + self.check_build_layout() self.state['buildozer:defaultcommand'] = args def cmd_version(self, *args): @@ -497,6 +523,25 @@ class Buildozer(object): ''' print 'Buildozer {0}'.format(__version__) + # + # Private + # + + def _get_config_list(self, section, token, default=None): + # monkey-patch method for ConfigParser + # get a key as a list of string, seperated from the comma + values = self.config.getdefault(section, token, default).split(',') + return [x.strip() for x in values] + + def _get_config_default(self, section, token, default=None): + # monkey-patch method for ConfigParser + # get a key in a section, or the default + if not self.config.has_section(section): + return default + if not self.config.has_option(section, token): + return default + return self.config.get(section, token) + def run(): Buildozer().run_command(sys.argv[1:]) diff --git a/buildozer/targets/android.py b/buildozer/targets/android.py index a7dd0ba..17b0a2e 100644 --- a/buildozer/targets/android.py +++ b/buildozer/targets/android.py @@ -130,26 +130,26 @@ class TargetAndroid(Target): def _install_apache_ant(self): ant_dir = self.apache_ant_dir if self.buildozer.file_exists(ant_dir): - self.buildozer.log('Apache ANT found at {0}'.format(ant_dir)) + self.buildozer.info('Apache ANT found at {0}'.format(ant_dir)) return ant_dir - self.buildozer.log('Android ANT is missing, downloading') + 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, cwd=self.buildozer.global_platform_dir) self.buildozer.file_extract(archive, cwd=self.buildozer.global_platform_dir) - self.buildozer.log('Apache ANT installation done.') + self.buildozer.info('Apache ANT installation done.') return ant_dir def _install_android_sdk(self): sdk_dir = self.android_sdk_dir if self.buildozer.file_exists(sdk_dir): - self.buildozer.log('Android SDK found at {0}'.format(sdk_dir)) + self.buildozer.info('Android SDK found at {0}'.format(sdk_dir)) return sdk_dir - self.buildozer.log('Android SDK is missing, downloading') + self.buildozer.info('Android SDK is missing, downloading') if platform in ('win32', 'cygwin'): archive = 'android-sdk_r{0}-windows.zip' unpacked = 'android-sdk-windows' @@ -167,21 +167,21 @@ class TargetAndroid(Target): self.buildozer.download(url, archive, cwd=self.buildozer.global_platform_dir) - self.buildozer.log('Unpacking Android SDK') + self.buildozer.info('Unpacking Android SDK') self.buildozer.file_extract(archive, cwd=self.buildozer.global_platform_dir) self.buildozer.file_rename(unpacked, sdk_dir, cwd=self.buildozer.global_platform_dir) - self.buildozer.log('Android SDK installation done.') + self.buildozer.info('Android SDK installation done.') return sdk_dir def _install_android_ndk(self): ndk_dir = self.android_ndk_dir if self.buildozer.file_exists(ndk_dir): - self.buildozer.log('Android NDK found at {0}'.format(ndk_dir)) + self.buildozer.info('Android NDK found at {0}'.format(ndk_dir)) return ndk_dir - self.buildozer.log('Android NDK is missing, downloading') + self.buildozer.info('Android NDK is missing, downloading') if platform in ('win32', 'cygwin'): archive = 'android-ndk-r{0}-windows.zip' elif platform in ('darwin', ): @@ -198,12 +198,12 @@ class TargetAndroid(Target): self.buildozer.download(url, archive, cwd=self.buildozer.global_platform_dir) - self.buildozer.log('Unpacking Android NDK') + self.buildozer.info('Unpacking Android NDK') self.buildozer.file_extract(archive, cwd=self.buildozer.global_platform_dir) self.buildozer.file_rename(unpacked, ndk_dir, cwd=self.buildozer.global_platform_dir) - self.buildozer.log('Android NDK installation done.') + self.buildozer.info('Android NDK installation done.') return ndk_dir def _install_android_packages(self): @@ -215,12 +215,12 @@ class TargetAndroid(Target): if not self.buildozer.file_exists(self.android_sdk_dir, 'platform-tools'): packages.append('platform-tools') if not packages: - self.buildozer.log('Android packages already installed.') + self.buildozer.info('Android packages already installed.') return self.buildozer.cmd('{0} update sdk -u -a -t {1}'.format( self.android_cmd, ','.join(packages)), cwd=self.buildozer.global_platform_dir) - self.buildozer.log('Android packages installation done.') + self.buildozer.info('Android packages installation done.') def install_platform(self): cmd = self.buildozer.cmd @@ -276,14 +276,14 @@ class TargetAndroid(Target): need_compile = 1 if not need_compile: - self.buildozer.log('Distribution already compiled, pass.') + self.buildozer.info('Distribution already compiled, pass.') return modules_str = ' '.join(android_requirements) cmd = self.buildozer.cmd cmd('git clean -dxf', cwd=self.pa_dir) cmd('./distribute.sh -m "{0}"'.format(modules_str), cwd=self.pa_dir) - self.buildozer.log('Distribution compiled.') + self.buildozer.info('Distribution compiled.') # ensure we will not compile again self.buildozer.state['android.requirements'] = android_requirements @@ -346,8 +346,8 @@ class TargetAndroid(Target): copyfile(join(dist_dir, 'bin', apk), join(self.buildozer.bin_dir, apk)) - self.buildozer.log('Android packaging done!') - self.buildozer.log('APK {0} available in the bin directory'.format(apk)) + self.buildozer.info('Android packaging done!') + 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 @@ -373,7 +373,7 @@ class TargetAndroid(Target): self.buildozer.cmd('{0} install -r {1}'.format( self.adb_cmd, full_apk), cwd=self.buildozer.global_platform_dir) - self.buildozer.log('Application pushed on the device.') + self.buildozer.info('Application pushed on the device.') def cmd_run(self, *args): super(TargetAndroid, self).cmd_run(*args) @@ -387,7 +387,7 @@ class TargetAndroid(Target): adb=self.adb_cmd, package=package, entry=entrypoint), cwd=self.buildozer.global_platform_dir) - self.buildozer.log('Application started on the device.') + self.buildozer.info('Application started on the device.') def cmd_logcat(self, *args): '''Show the log from the device From f297fd5ac25391256a7ef27a26a302083a136b88 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Fri, 21 Dec 2012 02:00:29 +0100 Subject: [PATCH 25/32] add logging level capability. restrict to error+info by default. use --verbose/-v, or log_level=2 in the spec for increasing to debug, and show command output. --- README.rst | 2 +- buildozer/__init__.py | 46 ++++++++++++++++++++++++++---------- buildozer/default.spec | 4 ++++ buildozer/targets/android.py | 3 ++- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/README.rst b/README.rst index 29b745b..59f530e 100644 --- a/README.rst +++ b/README.rst @@ -54,7 +54,7 @@ Usage :: - Usage: buildozer [target] [command1] [command2] + Usage: buildozer [--verbose] [target] [command1] [command2] Available targets: android Android target, based on python-for-android project diff --git a/buildozer/__init__.py b/buildozer/__init__.py index c8a7052..b567b6c 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -43,6 +43,7 @@ class Buildozer(object): def __init__(self, filename='buildozer.spec', target=None): super(Buildozer, self).__init__() + self.log_level = 1 self.environ = {} self.specfilename = filename self.state = None @@ -51,6 +52,12 @@ class Buildozer(object): self.config.getdefault = self._get_config_default self.config.read(filename) + try: + self.log_level = int(self.config.getdefault( + 'buildozer', 'log_level', '1')) + except: + pass + self.check_configuration_tokens() self.targetname = None @@ -111,6 +118,8 @@ class Buildozer(object): self.target._build_done = True def log(self, level, msg): + if level > self.log_level: + return if USE_COLOR: color = COLOR_SEQ.format(30 + LOG_LEVELS_C[level]) print ''.join((RESET_SEQ, color, '# ', msg, RESET_SEQ)) @@ -141,9 +150,6 @@ class Buildozer(object): #print ' '.join(['{0}={1}'.format(*args) for args in # self.environ.iteritems()]) - get_stdout = kwargs.pop('get_stdout', False) - get_stderr = kwargs.pop('get_stderr', False) - # prepare the environ, based on the system + our own env env = copy(environ) env.update(self.environ) @@ -154,6 +160,12 @@ class Buildozer(object): kwargs.setdefault('stderr', PIPE) kwargs.setdefault('close_fds', True) kwargs.setdefault('shell', True) + kwargs.setdefault('show_output', self.log_level > 1) + + show_output = kwargs.pop('show_output') + get_stdout = kwargs.pop('get_stdout', False) + get_stderr = kwargs.pop('get_stderr', False) + self.debug('Run %r' % command) # open the process @@ -179,14 +191,16 @@ class Buildozer(object): break if get_stdout: ret_stdout.append(chunk) - stdout.write(chunk) + if show_output: + stdout.write(chunk) if fd_stderr in readx: chunk = process.stderr.read() if chunk == '': break if get_stderr: ret_stderr.append(chunk) - stderr.write(chunk) + if show_output: + stderr.write(chunk) stdout.flush() stderr.flush() @@ -412,7 +426,7 @@ class Buildozer(object): pass def usage(self): - print 'Usage: buildozer [target] [command1] [command2]' + print 'Usage: buildozer [--verbose] [target] [command1] [command2]' print print 'Available targets:' targets = list(self.targets()) @@ -464,13 +478,21 @@ class Buildozer(object): self.run_command(cmd) def run_command(self, args): - if '-h' in args or '--help' in args: - self.usage() - exit(0) + while args: + if not args[0].startswith('-'): + break + arg = args.pop(0) - if '--version' in args: - print 'Buildozer {0}'.format(__version__) - exit(0) + if arg in ('-v', '--verbose'): + self.log_level = 2 + + if arg in ('-h', '--help'): + self.usage() + exit(0) + + if args == '--version': + print 'Buildozer {0}'.format(__version__) + exit(0) if not args: self.run_default() diff --git a/buildozer/default.spec b/buildozer/default.spec index 9b3a117..983d14d 100644 --- a/buildozer/default.spec +++ b/buildozer/default.spec @@ -56,3 +56,7 @@ requirements = twisted,kivy # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity +[buildozer] + +# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) +log_level = 1 diff --git a/buildozer/targets/android.py b/buildozer/targets/android.py index 17b0a2e..c0ce3ac 100644 --- a/buildozer/targets/android.py +++ b/buildozer/targets/android.py @@ -394,7 +394,8 @@ class TargetAndroid(Target): ''' self.check_requirements() self.buildozer.cmd('{adb} logcat'.format(adb=self.adb_cmd), - cwd=self.buildozer.global_platform_dir) + cwd=self.buildozer.global_platform_dir, + show_output=True) From d7e7d748b169a514a97f8977b22fed1a00b61cfc Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Fri, 21 Dec 2012 02:11:44 +0100 Subject: [PATCH 26/32] add initial .spec tokens checks --- buildozer/__init__.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/buildozer/__init__.py b/buildozer/__init__.py index b567b6c..bffd8b6 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -117,6 +117,10 @@ class Buildozer(object): # flag to prevent multiple build self.target._build_done = True + # + # Log functions + # + def log(self, level, msg): if level > self.log_level: return @@ -135,6 +139,10 @@ class Buildozer(object): def error(self, msg): self.log(0, msg) + # + # Internal check methods + # + def checkbin(self, msg, fn): self.debug('Search for {0}'.format(msg)) if exists(fn): @@ -218,6 +226,34 @@ class Buildozer(object): '''Ensure the spec file is 'correct'. ''' self.info('Check configuration tokens') + get = self.config.getdefault + errors = [] + adderror = errors.append + if not get('app', 'title', ''): + adderror('[app] "title" is missing') + if not get('app', 'package.name', ''): + adderror('[app] "package.name" is missing') + if not get('app', 'source.dir', ''): + adderror('[app] "source.dir" is missing') + + version = get('app', 'version', '') + version_regex = get('app', 'version.regex', '') + if not version and not version_regex: + adderror('[app] One of "version" or "version.regex" must be set') + if version and version_regex: + adderror('[app] Conflict between "version" and "version.regex"' + ', only one can be used.') + if version_regex and not get('app', 'version.filename', ''): + adderror('[app] "version.filename" is missing' + ', required by "version.regex"') + + if errors: + self.error('{0} errors found in the buildozer.spec'.format( + len(errors))) + for error in errors: + print error + exit(1) + def check_build_layout(self): '''Ensure the build (local and global) directory layout and files are From fd9cc88a762124f47b1ce067a2c4f05d302fbc6f Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Fri, 21 Dec 2012 02:33:16 +0100 Subject: [PATCH 27/32] add target configuration check (like ensure the android permissions are the correct one, according to the platform sdk). --- buildozer/__init__.py | 3 ++- buildozer/target.py | 16 +++++++++--- buildozer/targets/android.py | 49 ++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/buildozer/__init__.py b/buildozer/__init__.py index bffd8b6..f08ec88 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -72,6 +72,7 @@ class Buildozer(object): m = __import__('buildozer.targets.%s' % target, fromlist=['buildozer']) self.target = m.get_target(self) self.check_build_layout() + self.target.check_configuration_tokens() def prepare_for_build(self): '''Prepare the build. @@ -248,7 +249,7 @@ class Buildozer(object): ', required by "version.regex"') if errors: - self.error('{0} errors found in the buildozer.spec'.format( + self.error('{0} error(s) found in the buildozer.spec'.format( len(errors))) for error in errors: print error diff --git a/buildozer/target.py b/buildozer/target.py index 08e0a1f..6f68fbb 100644 --- a/buildozer/target.py +++ b/buildozer/target.py @@ -11,6 +11,16 @@ class Target(object): def check_requirements(self): pass + def check_configuration_tokens(self, errors=None): + if errors: + self.buildozer.info('Check target configuration tokens') + self.buildozer.error( + '{0} error(s) found in the buildozer.spec'.format( + len(errors))) + for error in errors: + print error + exit(1) + def compile_platform(self): pass @@ -26,7 +36,7 @@ class Target(object): def run_commands(self, args): if not args: - print 'ERROR: missing target command' + self.buildozer.error('Missing target command') self.buildozer.usage() exit(1) @@ -40,7 +50,7 @@ class Target(object): last_command.append(arg) else: if not last_command: - print 'ERROR: argument passed without a command' + self.buildozer.error('Argument passed without a command') self.buildozer.usage() exit(1) last_command.append(arg) @@ -50,7 +60,7 @@ class Target(object): for item in result: command, args = item[0], item[1:] if not hasattr(self, 'cmd_{0}'.format(command)): - print 'Unknown command {0}'.format(command) + self.buildozer.error('Unknow command {0}'.format(command)) exit(1) getattr(self, 'cmd_{0}'.format(command))(args) diff --git a/buildozer/targets/android.py b/buildozer/targets/android.py index c0ce3ac..8611caf 100644 --- a/buildozer/targets/android.py +++ b/buildozer/targets/android.py @@ -108,6 +108,51 @@ class TargetAndroid(Target): checkbin('Java compiler', self.javac_cmd) checkbin('Java keytool', self.keytool_cmd) + def check_configuration_tokens(self): + errors = [] + + # check the permission + available_permissions = self._get_available_permissions() + if available_permissions: + permissions = self.buildozer.config.getlist( + 'app', 'android.permissions', []) + for permission in permissions: + if permission not in available_permissions: + errors.append( + '[app] "android.permission" contain an unknown' + ' permission {0}'.format(permission)) + + super(TargetAndroid, self).check_configuration_tokens(errors) + + def _get_available_permissions(self): + key = 'android:available_permissions' + key_sdk = 'android:available_permissions_sdk' + + refresh_permissions = False + sdk = self.buildozer.state.get(key_sdk, None) + if not sdk or sdk != self.android_sdk_version: + refresh_permissions = True + if key not in self.buildozer.state: + refresh_permissions = True + if not refresh_permissions: + return self.buildozer.state[key] + + try: + 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') + with open(fn) as fd: + doc = ET.fromstring(fd.read()) + 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 + self.buildozer.state[key_sdk] = self.android_sdk_version + return available_permissions + except: + return None + def _set_win32_java_home(self): if 'JAVA_HOME' in self.buildozer.environ: return @@ -237,6 +282,10 @@ class TargetAndroid(Target): self._install_android_ndk() self._install_android_packages() + # ultimate configuration check. + # some of our configuration cannot be check without platform. + self.check_configuration_tokens() + self.buildozer.environ.update({ 'ANDROIDSDK': self.android_sdk_dir, 'ANDROIDNDK': self.android_ndk_dir, From b2300e2bd99472abc5df78386ddb55602c1003f8 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Fri, 21 Dec 2012 03:55:20 +0100 Subject: [PATCH 28/32] fix debug() issue, and avoid % in print. --- buildozer/__init__.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/buildozer/__init__.py b/buildozer/__init__.py index f08ec88..53bb553 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -69,7 +69,8 @@ class Buildozer(object): '''Set the target to use (one of buildozer.targets, such as "android") ''' self.targetname = target - m = __import__('buildozer.targets.%s' % target, fromlist=['buildozer']) + m = __import__('buildozer.targets.{0}'.format(target), + fromlist=['buildozer']) self.target = m.get_target(self) self.check_build_layout() self.target.check_configuration_tokens() @@ -83,7 +84,7 @@ class Buildozer(object): self.info('Preparing build') - self.info('Check requirements for %s' % self.targetname) + self.info('Check requirements for {0}'.format(self.targetname)) self.target.check_requirements() self.info('Install platform') @@ -175,7 +176,7 @@ class Buildozer(object): get_stdout = kwargs.pop('get_stdout', False) get_stderr = kwargs.pop('get_stderr', False) - self.debug('Run %r' % command) + self.debug('Run {0!r}'.format(command)) # open the process process = Popen(command, **kwargs) @@ -285,7 +286,7 @@ class Buildozer(object): def mkdir(self, dn): if exists(dn): return - self.debug('Create directory {0}', dn) + self.debug('Create directory {0}'.format(dn)) makedirs(dn) def file_exists(self, *args): @@ -373,7 +374,7 @@ class Buildozer(object): match = search(regex, data) if not match: raise Exception( - 'Unable to found capture version in %r' % fn) + 'Unable to found capture version in {0}'.format(fn)) version = match.groups()[0] self.debug('Captured version: {0}'.format(version)) return version @@ -457,7 +458,8 @@ class Buildozer(object): continue target = fn[:-3] try: - m = __import__('buildozer.targets.%s' % target, fromlist=['buildozer']) + m = __import__('buildozer.targets.{0}'.format(target), + fromlist=['buildozer']) yield target, m except: pass From 47fbfbe209e1d1c484f6a80a5be957a8db811a92 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 24 Dec 2012 00:27:47 +0100 Subject: [PATCH 29/32] check configuration token when target is set --- buildozer/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/buildozer/__init__.py b/buildozer/__init__.py index 53bb553..20f897a 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -17,6 +17,7 @@ import zipfile import sys import fcntl import os +import re from select import select from sys import stdout, stderr, exit from urllib import urlretrieve @@ -58,8 +59,6 @@ class Buildozer(object): except: pass - self.check_configuration_tokens() - self.targetname = None self.target = None if target: @@ -73,6 +72,7 @@ class Buildozer(object): fromlist=['buildozer']) self.target = m.get_target(self) self.check_build_layout() + self.check_configuration_tokens() self.target.check_configuration_tokens() def prepare_for_build(self): @@ -418,6 +418,12 @@ class Buildozer(object): self.debug('Copy {0}'.format(sfn)) copyfile(sfn, rfn) + def namify(self, name): + '''Return a "valid" name from a name with lot of invalid chars + (allowed characters: a-z, A-Z, 0-9, -, _) + ''' + return re.sub('[^a-zA-Z0-9_\-]', '_', name) + @property def buildozer_dir(self): return realpath(join( From b732490b2c96cb4340552c07d28e54f0402878b7 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 24 Dec 2012 00:27:59 +0100 Subject: [PATCH 30/32] add missing default methods in target.py --- buildozer/target.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/buildozer/target.py b/buildozer/target.py index 6f68fbb..f4b961f 100644 --- a/buildozer/target.py +++ b/buildozer/target.py @@ -24,6 +24,9 @@ class Target(object): def compile_platform(self): pass + def install_platform(self): + pass + def get_custom_commands(self): result = [] for x in dir(self): From cd439458c5278cfb1f280fedfdc2c02178c95821 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 24 Dec 2012 00:28:24 +0100 Subject: [PATCH 31/32] self.error doesn't exit --- buildozer/targets/android.py | 1 + 1 file changed, 1 insertion(+) diff --git a/buildozer/targets/android.py b/buildozer/targets/android.py index 8611caf..72105ce 100644 --- a/buildozer/targets/android.py +++ b/buildozer/targets/android.py @@ -317,6 +317,7 @@ class TargetAndroid(Target): 'Cannot package the app cause of the missing' ' requirements in python-for-android: {0}'.format( missing_requirements)) + exit(1) need_compile = 0 if last_requirements != android_requirements: From 56d5cc490f3b14f6c97fbc48cf9d847db5f85078 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 24 Dec 2012 00:29:07 +0100 Subject: [PATCH 32/32] more work on iOS target. --- buildozer/targets/ios.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/buildozer/targets/ios.py b/buildozer/targets/ios.py index 4d74006..471f7ad 100644 --- a/buildozer/targets/ios.py +++ b/buildozer/targets/ios.py @@ -1,7 +1,9 @@ ''' iOS target, based on kivy-ios project. (not working yet.) ''' + from buildozer.target import Target +from os.path import join class TargetIos(Target): @@ -13,7 +15,7 @@ class TargetIos(Target): checkbin('Xcode xcode-select', 'xcode-select') checkbin('Git git', 'git') - print 'Check availability of a iPhone SDK' + self.buildozer.debug('Check availability of a iPhone SDK') sdk = cmd('xcodebuild -showsdks | fgrep "iphoneos" | tail -n 1 | awk \'{print $2}\'')[0] if not sdk: raise Exception( @@ -21,18 +23,33 @@ class TargetIos(Target): else: print ' -> found %r' % sdk - print 'Check Xcode path' + self.buildozer.debug('Check Xcode path') xcode = cmd('xcode-select -print-path')[0] if not xcode: raise Exception('Unable to get xcode path') - print ' -> found %r' % xcode + self.buildozer.debug(' -> found {0}'.format(xcode)) def install_platform(self): cmd = self.buildozer.cmd - cmd('git clone git://github.com/kivy/kivy-ios', - cwd=self.buildozer.platform_dir) + self.ios_dir = ios_dir = join(self.buildozer.platform_dir, 'kivy-ios') + if not self.buildozer.file_exists(ios_dir): + cmd('git clone git://github.com/kivy/kivy-ios', + cwd=self.buildozer.platform_dir) + elif self.platform_update: + cmd('git clean -dxf', cwd=ios_dir) + cmd('git pull origin master', cwd=ios_dir) + def compile_platform(self): + self.buildozer.cmd('tools/build-all.sh', cwd=self.ios_dir) + def build_package(self): + # create the project + app_name = self.buildozer.namify(self.config.get('app', 'title')) + + self.app_project_dir = join(self.ios_dir, 'app-{0}'.format(app_name)) + self.buildozer.cmd('tools/create-xcode-project.sh {0} {1}'.format( + app_name, self.buildozer.app_dir), + cwd=self.ios_dir) def get_target(buildozer):