rework command line arguments / target / usage

add deploy and run command.
This commit is contained in:
Mathieu Virbel 2012-12-19 17:35:48 +01:00
parent 66f6e46c4f
commit 262922472f
6 changed files with 396 additions and 54 deletions

View file

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

View file

@ -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 <command args...>"'
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:])

46
buildozer/default.spec Normal file
View file

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

View file

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

View file

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

View file

@ -1,3 +1,6 @@
'''
iOS target, based on kivy-ios project. (not working yet.)
'''
from buildozer.target import Target
class TargetIos(Target):