rework command line arguments / target / usage
add deploy and run command.
This commit is contained in:
parent
66f6e46c4f
commit
262922472f
6 changed files with 396 additions and 54 deletions
70
README.rst
70
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
|
||||
|
||||
|
|
|
@ -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
46
buildozer/default.spec
Normal 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
|
||||
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
'''
|
||||
iOS target, based on kivy-ios project. (not working yet.)
|
||||
'''
|
||||
from buildozer.target import Target
|
||||
|
||||
class TargetIos(Target):
|
||||
|
|
Loading…
Add table
Reference in a new issue