Conflicts:
	.gitignore
This commit is contained in:
tshirtman 2012-12-27 22:22:37 +01:00
commit 3a6ba988bd
12 changed files with 1395 additions and 0 deletions

1
.gitignore vendored
View file

@ -1,6 +1,7 @@
*.py[co]
# Packages
.*.swp
*.egg
*.egg-info
dist

7
COPYING Normal file
View file

@ -0,0 +1,7 @@
Copyright (c) 2012 Mathieu Virbel <mat@kivy.org>
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.

2
MANIFEST.in Normal file
View file

@ -0,0 +1,2 @@
include *COPYING
recursive-include buildozer *.spec

84
README.rst Normal file
View file

@ -0,0 +1,84 @@
Buildozer
=========
THIS IS A WORK IN PROGRESS, DO NOT USE.
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
spec for create package for Android, iOS, Windows, OSX and Linux.
Usage example
-------------
#. 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 init
# edit the buildozer.spec, then
buildozer android debug deploy run
Example of commands::
# buildozer commands
buildozer clean
# buildozer target command
buildozer android update
buildozer android deploy
buildozer android debug
buildozer android release
# 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 deploy run
Usage
-----
::
Usage: buildozer [--verbose] [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
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 Deploy the application on the device
run Run the application on the device
buildozer.spec
--------------
See `buildozer/default.spec <https://raw.github.com/kivy/buildozer/master/buildozer/default.spec>`_ for an up-to-date spec file.

615
buildozer/__init__.py Normal file
View file

@ -0,0 +1,615 @@
'''
Layout directory for buildozer:
build/
<targetname>/
platform/ - all the platform files necessary
app/ - compiled application
'''
__version__ = '0.3-dev'
import shelve
import zipfile
import sys
import fcntl
import os
import re
from select import select
from sys import stdout, stderr, exit
from urllib import urlretrieve
from re import search
from ConfigParser import SafeConfigParser
from os.path import join, exists, dirname, realpath, splitext, expanduser
from subprocess import Popen, PIPE
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):
standard_cmds = ('clean', 'update', 'debug', 'release', 'deploy', 'run')
def __init__(self, filename='buildozer.spec', target=None):
super(Buildozer, self).__init__()
self.log_level = 1
self.environ = {}
self.specfilename = filename
self.state = None
self.config = SafeConfigParser()
self.config.getlist = self._get_config_list
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.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.{0}'.format(target),
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):
'''Prepare the build.
'''
assert(self.target is not None)
if hasattr(self.target, '_build_prepared'):
return
self.info('Preparing build')
self.info('Check requirements for {0}'.format(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
#
# Log functions
#
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))
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):
self.log(0, msg)
#
# Internal check methods
#
def checkbin(self, msg, fn):
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.debug(' -> found at {0}'.format(rfn))
return rfn
raise Exception(msg + 'not found')
def cmd(self, command, **kwargs):
#print ' '.join(['{0}={1}'.format(*args) for args in
# self.environ.iteritems()])
# prepare the environ, based on the system + our own env
env = copy(environ)
env.update(self.environ)
# prepare the process
kwargs.setdefault('env', env)
kwargs.setdefault('stdout', PIPE)
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 {0!r}'.format(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)
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)
if show_output:
stderr.write(chunk)
stdout.flush()
stderr.flush()
process.communicate()
if process.returncode != 0:
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 check_configuration_tokens(self):
'''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} error(s) 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
ready.
'''
self.info('Ensure build layout')
if not exists(self.specfilename):
print 'No {0} found in the current directory. Abandon.'.format(
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:
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
self.debug('Create directory {0}'.format(dn))
makedirs(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)
self.debug('Rename {0} to {1}'.format(source, 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 clean_platform(self):
self.info('Clean the platform build directory')
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:
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.debug('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', '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 {0}'.format(fn))
version = match.groups()[0]
self.debug('Captured version: {0}'.format(version))
return version
raise Exception('Missing version or version.regex + version.filename')
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', '')
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.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(
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
#
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.{0}'.format(target),
fromlist=['buildozer'])
yield target, m
except:
pass
def usage(self):
print 'Usage: buildozer [--verbose] [target] [command1] [command2]'
print
print 'Available targets:'
targets = list(self.targets())
for target, m in targets:
doc = m.__doc__.strip().splitlines()[0].strip()
print ' {0:<18} {1}'.format(target, 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)
doc = [x for x in
meth.__doc__.strip().splitlines()][0].strip()
print ' {0:<18} {1}'.format(name, doc)
print
print 'Target commands:'
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'
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
def run_default(self):
self.check_build_layout()
if 'buildozer:defaultcommand' not in self.state:
print 'No default command set.'
print 'Use "buildozer setdefault <command args...>"'
exit(1)
cmd = self.state['buildozer:defaultcommand']
self.run_command(cmd)
def run_command(self, args):
while args:
if not args[0].startswith('-'):
break
arg = args.pop(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()
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 cmd_init(self, *args):
'''Create a initial buildozer.spec in the current directory
'''
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):
'''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.check_build_layout()
self.state['buildozer:defaultcommand'] = args
def cmd_version(self, *args):
'''Show the Buildozer version
'''
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:])

62
buildozer/default.spec Normal file
View file

@ -0,0 +1,62 @@
[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) Android API to use
#android.api = 14
# (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
[buildozer]
# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
log_level = 1

92
buildozer/target.py Normal file
View file

@ -0,0 +1,92 @@
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 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
def install_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:
self.buildozer.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:
self.buildozer.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)):
self.buildozer.error('Unknow 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()

View file

View file

@ -0,0 +1,453 @@
'''
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
# project!
#
ANDROID_API = '14'
ANDROID_MINAPI = '8'
ANDROID_SDK_VERSION = '21'
ANDROID_NDK_VERSION = '8c'
APACHE_ANT_VERSION = '1.8.4'
import traceback
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):
@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(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(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(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')
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 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
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 = self.apache_ant_dir
if self.buildozer.file_exists(ant_dir):
self.buildozer.info('Apache ANT found at {0}'.format(ant_dir))
return ant_dir
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.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.info('Android SDK found at {0}'.format(sdk_dir))
return sdk_dir
self.buildozer.info('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(self.android_sdk_version)
url = 'http://dl.google.com/android/'
self.buildozer.download(url, archive,
cwd=self.buildozer.global_platform_dir)
self.buildozer.info('Unpacking Android SDK')
self.buildozer.file_extract(archive,
cwd=self.buildozer.global_platform_dir)
self.buildozer.file_rename(unpacked, sdk_dir,
cwd=self.buildozer.global_platform_dir)
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.info('Android NDK found at {0}'.format(ndk_dir))
return ndk_dir
self.buildozer.info('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(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.global_platform_dir)
self.buildozer.info('Unpacking Android NDK')
self.buildozer.file_extract(archive,
cwd=self.buildozer.global_platform_dir)
self.buildozer.file_rename(unpacked, ndk_dir,
cwd=self.buildozer.global_platform_dir)
self.buildozer.info('Android NDK installation done.')
return ndk_dir
def _install_android_packages(self):
packages = []
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(self.android_api))
if not self.buildozer.file_exists(self.android_sdk_dir, 'platform-tools'):
packages.append('platform-tools')
if not packages:
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.info('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)
elif self.platform_update:
cmd('git clean -dxf', cwd=pa_dir)
cmd('git pull origin master', cwd=pa_dir)
self._install_apache_ant()
self._install_android_sdk()
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,
'ANDROIDAPI': ANDROID_API,
'ANDROIDNDKVER': self.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.getlist('app',
'requirements', '')
# we need to extract the requirements that python-for-android knows
# about
available_modules = self.buildozer.cmd(
'./distribute.sh -l', cwd=self.pa_dir, get_stdout=True)[0]
if not available_modules.startswith('Available modules:'):
self.buildozer.error('Python-for-android invalid output for -l')
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))
exit(1)
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.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.info('Distribution compiled.')
# ensure we will not compile again
self.buildozer.state['android.requirements'] = android_requirements
self.buildozer.state.sync()
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 = (
'{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.
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}-{mode}.apk'.format(
title=apktitle, version=version, mode=mode)
# copy to our place
copyfile(join(dist_dir, 'bin', apk),
join(self.buildozer.bin_dir, apk))
self.buildozer.info('Android packaging done!')
self.buildozer.info('APK {0} available in the bin directory'.format(apk))
self.buildozer.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.global_platform_dir)
self.buildozer.info('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.global_platform_dir)
self.buildozer.info('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,
show_output=True)
def get_target(buildozer):
return TargetAndroid(buildozer)

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

@ -0,0 +1,56 @@
'''
iOS target, based on kivy-ios project. (not working yet.)
'''
from buildozer.target import Target
from os.path import join
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')
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(
'No iPhone SDK found. Please install at least one iOS SDK.')
else:
print ' -> found %r' % sdk
self.buildozer.debug('Check Xcode path')
xcode = cmd('xcode-select -print-path')[0]
if not xcode:
raise Exception('Unable to get xcode path')
self.buildozer.debug(' -> found {0}'.format(xcode))
def install_platform(self):
cmd = self.buildozer.cmd
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):
return TargetIos(buildozer)

18
setup.py Normal file
View file

@ -0,0 +1,18 @@
from distutils.core import setup
import buildozer
setup(
name='buildozer',
version=buildozer.__version__,
author='Mathieu Virbel',
author_email='mat@kivy.org',
url='http://github.com/kivy/buildozer',
license='MIT',
packages=[
'buildozer',
'buildozer.targets'],
package_data={'buildozer': ['default.spec']},
scripts=['tools/buildozer'],
description='Generic Python packager for Android / iOS and Desktop'
)

5
tools/buildozer Executable file
View file

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