first commit
This commit is contained in:
commit
a4173cceda
8 changed files with 556 additions and 0 deletions
27
.gitignore
vendored
Normal file
27
.gitignore
vendored
Normal 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
42
README.rst
Normal 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
5
buildozer.py
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
if __name__ == '__main__':
|
||||
from buildozer import run
|
||||
run()
|
183
buildozer/__init__.py
Normal file
183
buildozer/__init__.py
Normal 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
12
buildozer/target.py
Normal 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
|
0
buildozer/targets/__init__.py
Normal file
0
buildozer/targets/__init__.py
Normal file
251
buildozer/targets/android.py
Normal file
251
buildozer/targets/android.py
Normal 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
36
buildozer/targets/ios.py
Normal 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)
|
Loading…
Add table
Reference in a new issue