first pass to install possible external requirements within a virtualenv + copy the installed packages into an _applibs + patch main.py to include the _applibs.

This commit is contained in:
Mathieu Virbel 2013-01-10 02:46:53 +01:00
parent cf8ed00b9f
commit 5db8675ea3
3 changed files with 98 additions and 17 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"
@ -99,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()
@ -281,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:
@ -294,6 +299,56 @@ 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]
# ok now check the availability of all requirements
for requirement in requirements:
self._install_application_requirement(requirement)
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
@ -398,6 +453,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)]:
@ -419,7 +476,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)
@ -429,6 +486,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, -, _)
@ -453,6 +529,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')
@ -461,6 +541,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')
#
@ -529,6 +613,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)

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: