first commit

This commit is contained in:
Mathieu Virbel 2012-12-19 02:34:32 +01:00
commit a4173cceda
8 changed files with 556 additions and 0 deletions

27
.gitignore vendored Normal file
View file

@ -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

42
README.rst Normal file
View file

@ -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

5
buildozer.py Executable file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env python
if __name__ == '__main__':
from buildozer import run
run()

183
buildozer/__init__.py Normal file
View file

@ -0,0 +1,183 @@
'''
Layout directory for buildozer:
build/
<targetname>/
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()

12
buildozer/target.py Normal file
View file

@ -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

View file

View file

@ -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)

36
buildozer/targets/ios.py Normal file
View file

@ -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)