This commit is contained in:
tshirtman 2013-01-15 20:50:09 +01:00
commit e89fea178f
3 changed files with 135 additions and 19 deletions

View file

@ -27,7 +27,7 @@ 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
from shutil import copyfile, rmtree, copytree
RESET_SEQ = "\033[0m"
COLOR_SEQ = "\033[1;{0}m"
@ -38,6 +38,11 @@ USE_COLOR = 'NO_COLOR' not in environ
LOG_LEVELS_C = (RED, BLUE, BLACK)
LOG_LEVELS_T = 'EID'
class BuildozerCommandException(Exception):
pass
class Buildozer(object):
standard_cmds = ('clean', 'update', 'debug', 'release', 'deploy', 'run')
@ -51,7 +56,11 @@ class Buildozer(object):
self.config = SafeConfigParser()
self.config.getlist = self._get_config_list
self.config.getdefault = self._get_config_default
self.config.read(filename)
if exists(filename):
self.config.read(filename)
self.check_configuration_tokens()
try:
self.log_level = int(self.config.getdefault(
@ -90,6 +99,9 @@ class Buildozer(object):
self.info('Install platform')
self.target.install_platform()
self.info('Check application requirements')
self.check_application_requirements()
self.info('Compile platform')
self.target.compile_platform()
@ -177,6 +189,7 @@ class Buildozer(object):
get_stderr = kwargs.pop('get_stderr', False)
self.debug('Run {0!r}'.format(command))
self.debug('Cwd {}'.format(kwargs.get('cwd')))
# open the process
process = Popen(command, **kwargs)
@ -218,6 +231,7 @@ class Buildozer(object):
process.communicate()
if process.returncode != 0:
self.error('Command failed: {0}'.format(command))
raise BuildozerCommandException()
if ret_stdout:
ret_stdout = ''.join(ret_stdout)
if ret_stderr:
@ -270,11 +284,13 @@ class Buildozer(object):
# create global dir
self.mkdir(self.global_buildozer_dir)
self.mkdir(self.global_cache_dir)
# create local dir
specdir = dirname(self.specfilename)
self.mkdir(join(specdir, '.buildozer'))
self.mkdir(join(specdir, 'bin'))
self.mkdir(self.applibs_dir)
self.state = shelve.open(join(self.buildozer_dir, 'state.db'))
if self.targetname:
@ -283,12 +299,81 @@ class Buildozer(object):
self.mkdir(join(specdir, '.buildozer', target, 'platform'))
self.mkdir(join(specdir, '.buildozer', target, 'app'))
def check_application_requirements(self):
'''Ensure the application requirements are all available and ready to be
packaged as well.
'''
requirements = self.config.getlist('app', 'requirements', '')
target_available_packages = self.target.get_available_packages()
# remove all the requirements that the target can compile
requirements = [x for x in requirements if x not in
target_available_packages]
# did we already installed the libs ?
if exists(self.applibs_dir) and \
self.state.get('cache.applibs', '') == requirements:
self.debug('Application requirements already installed, pass')
return
# recreate applibs
self.rmdir(self.applibs_dir)
self.mkdir(self.applibs_dir)
# ok now check the availability of all requirements
for requirement in requirements:
self._install_application_requirement(requirement)
# everything goes as expected, save this state!
self.state['cache.applibs'] = requirements
def _install_application_requirement(self, module):
self._ensure_virtualenv()
self.debug('Install requirement {} in virtualenv'.format(module))
self.cmd('pip-2.7 install --download-cache={} --target={} {}'.format(
self.global_cache_dir, self.applibs_dir, module),
env=self.env_venv,
cwd=self.buildozer_dir)
def _ensure_virtualenv(self):
if hasattr(self, 'venv'):
return
self.venv = join(self.buildozer_dir, 'venv')
if not self.file_exists(self.venv):
self.cmd('virtualenv-2.7 --python=python2.7 ./venv',
cwd=self.buildozer_dir)
# read virtualenv output and parse it
output = self.cmd('bash -c "source venv/bin/activate && env"',
get_stdout=True,
cwd=self.buildozer_dir)
self.env_venv = copy(self.environ)
for line in output[0].splitlines():
args = line.split('=', 1)
if len(args) != 2:
continue
key, value = args
if key in ('VIRTUAL_ENV', 'PATH'):
self.env_venv[key] = value
if 'PYTHONHOME' in self.env_venv:
del self.env_venv['PYTHONHOME']
# ensure any sort of compilation will fail
self.env_venv['CC'] = '/bin/false'
self.env_venv['CXX'] = '/bin/false'
def mkdir(self, dn):
if exists(dn):
return
self.debug('Create directory {0}'.format(dn))
makedirs(dn)
def rmdir(self, dn):
if not exists(dn):
return
self.debug('Remove directory and subdirectory {}'.format(dn))
rmtree(dn)
def file_exists(self, *args):
return exists(join(*args))
@ -387,6 +472,8 @@ class Buildozer(object):
exclude_exts = self.config.getlist('app', 'source.exclude_exts', '')
app_dir = self.app_dir
rmtree(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)]:
@ -408,7 +495,7 @@ class Buildozer(object):
continue
sfn = join(root, fn)
rfn = realpath(join(app_dir, root[len(source_dir):], fn))
rfn = realpath(join(app_dir, root[len(source_dir) + 1:], fn))
# ensure the directory exists
dfn = dirname(rfn)
@ -418,6 +505,25 @@ class Buildozer(object):
self.debug('Copy {0}'.format(sfn))
copyfile(sfn, rfn)
# copy also the libs
copytree(self.applibs_dir, join(app_dir, '_applibs'))
# patch the main.py
main_py = join(app_dir, 'main.py')
if not self.file_exists(main_py):
self.error('Unable to patch main_py to add applibs directory.')
return
header = ('import sys, os; '
'sys.path = [os.path.join(os.path.dirname(__file__),'
'"_applibs")] + sys.path\n')
with open(main_py, 'rb') as fd:
data = fd.read()
data = header + data
with open(main_py, 'wb') as fd:
fd.write(data)
self.info('Patched main.py to include applibs')
def namify(self, name):
'''Return a "valid" name from a name with lot of invalid chars
(allowed characters: a-z, A-Z, 0-9, -, _)
@ -442,6 +548,10 @@ class Buildozer(object):
return realpath(join(
dirname(self.specfilename), 'bin'))
@property
def applibs_dir(self):
return join(self.buildozer_dir, 'applibs')
@property
def global_buildozer_dir(self):
return join(expanduser('~'), '.buildozer')
@ -450,6 +560,10 @@ class Buildozer(object):
def global_platform_dir(self):
return join(self.global_buildozer_dir, self.targetname, 'platform')
@property
def global_cache_dir(self):
return join(self.global_buildozer_dir, 'cache')
#
@ -518,6 +632,7 @@ class Buildozer(object):
if 'buildozer:defaultcommand' not in self.state:
print 'No default command set.'
print 'Use "buildozer setdefault <command args...>"'
print 'Use "buildozer help" for a list of all commands"'
exit(1)
cmd = self.state['buildozer:defaultcommand']
self.run_command(cmd)
@ -611,5 +726,10 @@ class Buildozer(object):
def run():
Buildozer().run_command(sys.argv[1:])
try:
Buildozer().run_command(sys.argv[1:])
except BuildozerCommandException:
# don't show the exception in the command line. The log already show the
# command failed.
pass

View file

@ -37,6 +37,9 @@ class Target(object):
result.append((x[4:], getattr(self, x).__doc__))
return result
def get_available_packages(self):
return ['kivy']
def run_commands(self, args):
if not args:
self.buildozer.error('Missing target command')

View file

@ -292,6 +292,13 @@ class TargetAndroid(Target):
'ANDROIDAPI': ANDROID_API,
'ANDROIDNDKVER': self.android_ndk_version})
def get_available_packages(self):
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')
return available_modules[19:].splitlines()[0].split()
def compile_platform(self):
# for android, the compilation depends really on the app requirements.
# compile the distribution only if the requirements changed.
@ -301,23 +308,9 @@ class TargetAndroid(Target):
# 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()
available_modules = self.get_available_packages()
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: