diff --git a/pythonforandroid/bootstraps/pygame/__init__.py b/pythonforandroid/bootstraps/pygame/__init__.py index 446d157e17..a0ddaac626 100644 --- a/pythonforandroid/bootstraps/pygame/__init__.py +++ b/pythonforandroid/bootstraps/pygame/__init__.py @@ -1,4 +1,4 @@ -from pythonforandroid.toolchain import Bootstrap, shprint, current_directory, info, warning, ArchAndroid, logger, info_main, which +from pythonforandroid.toolchain import Bootstrap, shprint, current_directory, info, warning, ArchAndroid, info_main from os.path import join, exists from os import walk import glob @@ -19,6 +19,9 @@ def run_distribute(self): # self.name) src_path = join(self.bootstrap_dir, 'build') + # AND: Hardcoding armeabi - naughty! + arch = ArchAndroid(self.ctx) + with current_directory(self.dist_dir): info('Creating initial layout') @@ -40,21 +43,14 @@ def run_distribute(self): info('Copying python distribution') hostpython = sh.Command(self.ctx.hostpython) # AND: This *doesn't* need to be in arm env? - shprint(hostpython, '-OO', '-m', 'compileall', self.ctx.get_python_install_dir()) + shprint(hostpython, '-OO', '-m', 'compileall', self.ctx.get_python_install_dir(), + _tail=10, _filterout="^Listing", _critical=True) if not exists('python-install'): shprint(sh.cp, '-a', self.ctx.get_python_install_dir(), './python-install') - info('Copying libs') - # AND: Hardcoding armeabi - naughty! - shprint(sh.mkdir, '-p', join('libs', 'armeabi')) - for lib in glob.glob(join(self.build_dir, 'libs', 'armeabi', '*')): - shprint(sh.cp, '-a', lib, join('libs', 'armeabi')) - for lib in glob.glob(join(self.ctx.get_libs_dir('armeabi'), '*')): - shprint(sh.cp, '-a', lib, join('libs', 'armeabi')) - - info('Copying java files') - for filename in glob.glob(self.ctx.javaclass_dir): - shprint(sh.cp, '-a', filename, 'src') + self.distribute_libs(arch, [join(self.build_dir, 'libs', arch.arch), self.ctx.get_libs_dir(arch.arch)]); + self.distribute_aars(arch) + self.distribute_javaclasses(self.ctx.javaclass_dir) info('Filling private directory') if not exists(join('private', 'lib')): @@ -91,21 +87,7 @@ def run_distribute(self): shprint(sh.rm, '-rf', 'lib-dynload/_testcapi.so') - info('Stripping libraries') - env = ArchAndroid(self.ctx).get_env() - strip = which('arm-linux-androideabi-strip', env['PATH']) - if strip is None: - warning('Can\'t find strip in PATH...') - strip = sh.Command(strip) - filens = shprint(sh.find, join(self.dist_dir, 'private'), join(self.dist_dir, 'libs'), - '-iname', '*.so', _env=env).stdout.decode('utf-8') - logger.info('Stripping libraries in private dir') - for filen in filens.split('\n'): - try: - strip(filen, _env=env) - except sh.ErrorReturnCode_1: - logger.debug('Failed to strip ' + 'filen') - + self.strip_libraries(arch) super(PygameBootstrap, self).run_distribute() bootstrap = PygameBootstrap() diff --git a/pythonforandroid/bootstraps/pygame/build/build.py b/pythonforandroid/bootstraps/pygame/build/build.py index 462dd29998..95f80a0444 100755 --- a/pythonforandroid/bootstraps/pygame/build/build.py +++ b/pythonforandroid/bootstraps/pygame/build/build.py @@ -183,7 +183,7 @@ def select(fn): tf = tarfile.open(tfn, 'w:gz', format=tarfile.USTAR_FORMAT) dirs = [] for fn, afn in files: - print('%s: %s' % (tfn, fn)) +# print('%s: %s' % (tfn, fn)) dn = dirname(afn) if dn not in dirs: # create every dirs first if not exist yet diff --git a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/AssetExtract.java b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/AssetExtract.java index e1d810798a..dd4ec48fc3 100644 --- a/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/AssetExtract.java +++ b/pythonforandroid/bootstraps/pygame/build/src/org/renpy/android/AssetExtract.java @@ -60,7 +60,7 @@ public boolean extractTar(String asset, String target) { break; } - Log.i("python", "extracting " + entry.getName()); + Log.v("python", "extracting " + entry.getName()); if (entry.isDirectory()) { diff --git a/pythonforandroid/bootstraps/sdl2/__init__.py b/pythonforandroid/bootstraps/sdl2/__init__.py index 1a4d1c1bbf..10c9e0f118 100644 --- a/pythonforandroid/bootstraps/sdl2/__init__.py +++ b/pythonforandroid/bootstraps/sdl2/__init__.py @@ -1,4 +1,4 @@ -from pythonforandroid.toolchain import Bootstrap, shprint, current_directory, info, warning, ArchAndroid, logger, info_main, which +from pythonforandroid.toolchain import Bootstrap, shprint, current_directory, info, warning, ArchAndroid, info_main from os.path import join, exists from os import walk import glob @@ -20,6 +20,9 @@ def run_distribute(self): with open('local.properties', 'w') as fileh: fileh.write('sdk.dir={}'.format(self.ctx.sdk_dir)) + # AND: Hardcoding armeabi - naughty! + arch = ArchAndroid(self.ctx) + with current_directory(self.dist_dir): info('Copying python distribution') @@ -31,19 +34,14 @@ def run_distribute(self): hostpython = sh.Command(self.ctx.hostpython) # AND: This *doesn't* need to be in arm env? shprint(hostpython, '-OO', '-m', 'compileall', - self.ctx.get_python_install_dir()) + self.ctx.get_python_install_dir(), + _tail=10, _filterout="^Listing", _critical=True) if not exists('python-install'): shprint(sh.cp, '-a', self.ctx.get_python_install_dir(), './python-install') - info('Copying libs') - # AND: Hardcoding armeabi - naughty! - shprint(sh.mkdir, '-p', join('libs', 'armeabi')) - for lib in glob.glob(join(self.ctx.get_libs_dir('armeabi'), '*')): - shprint(sh.cp, '-a', lib, join('libs', 'armeabi')) - - info('Copying java files') - for filename in glob.glob(self.ctx.javaclass_dir): - shprint(sh.cp, '-a', filename, 'src') + self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)]) + self.distribute_aars(arch) + self.distribute_javaclasses(self.ctx.javaclass_dir) info('Filling private directory') if not exists(join('private', 'lib')): @@ -81,21 +79,7 @@ def run_distribute(self): # shprint(sh.rm, '-rf', 'lib-dynload/_ctypes_test.so') # shprint(sh.rm, '-rf', 'lib-dynload/_testcapi.so') - - info('Stripping libraries') - env = ArchAndroid(self.ctx).get_env() - strip = which('arm-linux-androideabi-strip', env['PATH']) - if strip is None: - warning('Can\'t find strip in PATH...') - strip = sh.Command(strip) - filens = shprint(sh.find, join(self.dist_dir, 'private'), join(self.dist_dir, 'libs'), - '-iname', '*.so', _env=env).stdout.decode('utf-8') - logger.info('Stripping libraries in private dir') - for filen in filens.split('\n'): - try: - strip(filen, _env=env) - except sh.ErrorReturnCode_1: - logger.debug('Failed to strip ' + 'filen') + self.strip_libraries(arch) super(SDL2Bootstrap, self).run_distribute() bootstrap = SDL2Bootstrap() diff --git a/pythonforandroid/bootstraps/sdl2/build/build.py b/pythonforandroid/bootstraps/sdl2/build/build.py index 175931bec5..8192606565 100755 --- a/pythonforandroid/bootstraps/sdl2/build/build.py +++ b/pythonforandroid/bootstraps/sdl2/build/build.py @@ -166,7 +166,7 @@ def select(fn): tf = tarfile.open(tfn, 'w:gz', format=tarfile.USTAR_FORMAT) dirs = [] for fn, afn in files: - print('%s: %s' % (tfn, fn)) +# print('%s: %s' % (tfn, fn)) dn = dirname(afn) if dn not in dirs: # create every dirs first if not exist yet @@ -249,6 +249,10 @@ def make_package(args): default_icon = 'templates/kivy-icon.png' shutil.copy(args.icon or default_icon, 'res/drawable/icon.png') + default_presplash = 'templates/kivy-presplash.jpg' + shutil.copy(args.presplash or default_presplash, + 'res/drawable/presplash.jpg') + versioned_name = (args.name.replace(' ', '').replace('\'', '') + '-' + args.version) @@ -324,6 +328,14 @@ def parse_args(args=None): help='A png file to use as the icon for the application.') ap.add_argument('--permission', dest='permissions', action='append', help='The permissions to give this app.') + ap.add_argument('--meta-data', dest='meta_data', action='append', + help='Custom key=value to add in application metadata') + ap.add_argument('--presplash', dest='presplash', + help=('A jpeg file to use as a screen while the ' + 'application is loading.')) + ap.add_argument('--wakelock', dest='wakelock', action='store_true', + help=('Indicate if the application needs the device ' + 'to stay on')) if args is None: args = sys.argv[1:] @@ -333,6 +345,9 @@ def parse_args(args=None): if args.permissions is None: args.permissions = [] + if args.meta_data is None: + args.meta_data = [] + make_package(args) if __name__ == "__main__": diff --git a/pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/PythonActivity.java b/pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/PythonActivity.java index abe5b158d5..77979da1d7 100644 --- a/pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/PythonActivity.java +++ b/pythonforandroid/bootstraps/sdl2/build/src/org/kivy/android/PythonActivity.java @@ -7,23 +7,34 @@ import java.io.FileWriter; import java.io.File; +import android.view.ViewGroup; +import android.view.SurfaceView; import android.app.Activity; import android.util.Log; import android.widget.Toast; import android.os.Bundle; +import android.os.PowerManager; +import android.graphics.PixelFormat; +import android.view.SurfaceHolder; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ApplicationInfo; import org.libsdl.app.SDLActivity; import org.renpy.android.ResourceManager; import org.renpy.android.AssetExtract; + public class PythonActivity extends SDLActivity { private static final String TAG = "PythonActivity"; public static PythonActivity mActivity = null; - private ResourceManager resourceManager; - + private ResourceManager resourceManager = null; + private Bundle mMetaData = null; + private PowerManager.WakeLock mWakeLock = null; + @Override protected void onCreate(Bundle savedInstanceState) { Log.v(TAG, "My oncreate running"); @@ -48,6 +59,25 @@ protected void onCreate(Bundle savedInstanceState) { // nativeSetEnv("ANDROID_ARGUMENT", getFilesDir()); + + try { + Log.v(TAG, "Access to our meta-data..."); + this.mMetaData = this.mActivity.getPackageManager().getApplicationInfo( + this.mActivity.getPackageName(), PackageManager.GET_META_DATA).metaData; + + PowerManager pm = (PowerManager) this.mActivity.getSystemService(Context.POWER_SERVICE); + if ( this.mMetaData.getInt("wakelock") == 1 ) { + this.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On"); + } + if ( this.mMetaData.getInt("surface.transparent") != 0 ) { + Log.v(TAG, "Surface will be transparent."); + getSurface().setZOrderOnTop(true); + getSurface().getHolder().setFormat(PixelFormat.TRANSPARENT); + } else { + Log.i(TAG, "Surface will NOT be transparent"); + } + } catch (PackageManager.NameNotFoundException e) { + } } // This is just overrides the normal SDLActivity, which just loads @@ -169,6 +199,13 @@ public void unpackData(final String resource, File target) { Log.w("python", e); } } + } + + public static ViewGroup getLayout() { + return mLayout; + } + public static SurfaceView getSurface() { + return mSurface; } } diff --git a/pythonforandroid/bootstraps/sdl2/build/src/org/renpy/android/AssetExtract.java b/pythonforandroid/bootstraps/sdl2/build/src/org/renpy/android/AssetExtract.java index f1f077d7a1..52d6424e09 100644 --- a/pythonforandroid/bootstraps/sdl2/build/src/org/renpy/android/AssetExtract.java +++ b/pythonforandroid/bootstraps/sdl2/build/src/org/renpy/android/AssetExtract.java @@ -60,7 +60,7 @@ public boolean extractTar(String asset, String target) { break; } - Log.i("python", "extracting " + entry.getName()); + Log.v("python", "extracting " + entry.getName()); if (entry.isDirectory()) { diff --git a/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.xml.tmpl b/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.xml.tmpl index 75e3d62fa8..4ce2fe373a 100644 --- a/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.xml.tmpl +++ b/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.xml.tmpl @@ -38,6 +38,11 @@ android:allowBackup="true" android:theme="@android:style/Theme.NoTitleBar.Fullscreen" android:hardwareAccelerated="true" > + + {% for m in args.meta_data %} + {% endfor %} + + = value) { + return RAISE (PyExc_SDLError, "Invalid joystick trackball"); +@@ -300,7 +300,7 @@ joy_get_numhats (PyObject* self) + } + + value = SDL_JoystickNumHats (joy); +- printf("SDL_JoystickNumHats value:%d:\n", value); ++/* printf("SDL_JoystickNumHats value:%d:\n", value); */ + + return PyInt_FromLong (value); + } +@@ -327,7 +327,7 @@ joy_get_hat (PyObject* self, PyObject* args) + + px = py = 0; + value = SDL_JoystickGetHat (joy, _index); +- printf("SDL_JoystickGetHat value:%d:\n", value); ++/* printf("SDL_JoystickGetHat value:%d:\n", value); */ + + if (value & SDL_HAT_UP) { + py = 1; diff --git a/pythonforandroid/recipes/pygame_bootstrap_components/__init__.py b/pythonforandroid/recipes/pygame_bootstrap_components/__init__.py index 4ba1cb0b82..149cce167d 100644 --- a/pythonforandroid/recipes/pygame_bootstrap_components/__init__.py +++ b/pythonforandroid/recipes/pygame_bootstrap_components/__init__.py @@ -14,7 +14,7 @@ def prebuild_arch(self, arch): info('Unpacking pygame bootstrap JNI dir components') with current_directory(self.get_build_container_dir(arch)): if exists('sdl'): - info('sdl dir exists, so it looks like the JNI components', + info('sdl dir exists, so it looks like the JNI components' + 'are already unpacked. Skipping.') return for dirn in glob.glob(join(self.get_build_dir(arch), diff --git a/pythonforandroid/recipes/sdl/__init__.py b/pythonforandroid/recipes/sdl/__init__.py index 9f46d465f4..1da3999d26 100644 --- a/pythonforandroid/recipes/sdl/__init__.py +++ b/pythonforandroid/recipes/sdl/__init__.py @@ -18,7 +18,7 @@ def build_armeabi(self): env = ArchAndroid(self.ctx).get_env() with current_directory(self.get_jni_dir()): - shprint(sh.ndk_build, 'V=1', _env=env) + shprint(sh.ndk_build, 'V=1', _env=env, _tail=20, _critical=True) libs_dir = join(self.ctx.bootstrap.build_dir, 'libs', 'armeabi') import os diff --git a/pythonforandroid/recipes/vlc/__init__.py b/pythonforandroid/recipes/vlc/__init__.py new file mode 100644 index 0000000000..a10d9eb03c --- /dev/null +++ b/pythonforandroid/recipes/vlc/__init__.py @@ -0,0 +1,70 @@ +from pythonforandroid.toolchain import Recipe, shprint, current_directory, warning, info, debug +from os.path import exists, join +from os import environ +import sh +from colorama import Fore, Style + +class VlcRecipe(Recipe): + version = '3.0.0' + url = None + name = 'vlc' + + depends = [] + + port_git = 'http://git.videolan.org/git/vlc-ports/android.git' + vlc_git = 'http://git.videolan.org/git/vlc.git' + ENV_LIBVLC_AAR = 'LIBVLC_AAR' + aars = {} # for future use of multiple arch + + def prebuild_arch(self, arch): + super(VlcRecipe, self).prebuild_arch(arch) + build_dir = self.get_build_dir(arch.arch) + port_dir = join(build_dir, 'vlc-port-android') + if self.ENV_LIBVLC_AAR in environ: + self.aars[arch] = aar = environ.get(self.ENV_LIBVLC_AAR) + if not exists(aar): + warning("Error: libvlc-.aar bundle " \ + "not found in {}".format(aar)) + info("check {} environment!".format(self.ENV_LIBVLC_AAR)) + exit(1) + else: + aar_path = join(port_dir, 'libvlc', 'build', 'outputs', 'aar') + self.aars[arch] = aar = join(aar_path, 'libvlc-{}.aar'.format(self.version)) + warning("HINT: set path to precompiled libvlc-.aar bundle " \ + "in {} environment!".format(self.ENV_LIBVLC_AAR)) + info("libvlc-.aar should build " \ + "from sources at {}".format(port_dir)) + if not exists(join(port_dir, 'compile.sh')): + info("clone vlc port for android sources from {}".format( + self.port_git)) + shprint(sh.git, 'clone', self.port_git, port_dir, + _tail=20, _critical=True) + vlc_dir = join(port_dir, 'vlc') + if not exists(join(vlc_dir, 'Makefile.am')): + info("clone vlc sources from {}".format(self.vlc_git)) + shprint(sh.git, 'clone', self.vlc_git, vlc_dir, + _tail=20, _critical=True) + + def build_arch(self, arch): + super(VlcRecipe, self).build_arch(arch) + build_dir = self.get_build_dir(arch.arch) + port_dir = join(build_dir, 'vlc-port-android') + aar = self.aars[arch] + if not exists(aar): + with current_directory(port_dir): + env = dict(environ) + env.update({ + 'ANDROID_ABI': arch.arch, + 'ANDROID_NDK': self.ctx.ndk_dir, + 'ANDROID_SDK': self.ctx.sdk_dir, + }) + info("compiling vlc from sources") + debug("environment: {}".format(env)) + if not exists(join('bin', 'VLC-debug.apk')): + shprint(sh.Command('./compile.sh'), _env=env, + _tail=50, _critical=True) + shprint(sh.Command('./compile-libvlc.sh'), _env=env, + _tail=50, _critical=True) + shprint(sh.cp, '-a', aar, self.ctx.aars_dir) + +recipe = VlcRecipe() diff --git a/pythonforandroid/toolchain.py b/pythonforandroid/toolchain.py index 1599a243ec..84b5044c34 100755 --- a/pythonforandroid/toolchain.py +++ b/pythonforandroid/toolchain.py @@ -11,7 +11,7 @@ import sys from sys import stdout, stderr, platform from os.path import (join, dirname, realpath, exists, isdir, basename, - expanduser) + expanduser, splitext) from os import listdir, unlink, makedirs, environ, chdir, getcwd, walk, uname import os import zipfile @@ -30,6 +30,8 @@ from functools import wraps from datetime import datetime from distutils.spawn import find_executable +from tempfile import mkdtemp +from math import log10 try: from urllib.request import FancyURLopener except ImportError: @@ -50,6 +52,7 @@ def __init__(self): self._dict = defaultdict(str) def __getattr__(self, key): return self._dict[key] +Style = Fore = colorama_shim() Null_Style = Null_Fore = colorama_shim() if stdout.isatty(): @@ -130,6 +133,15 @@ def pretty_log_dists(dists, log_func=info): for line in infos: log_func('\t' + line) +def shorten_string(string, max_width): + ''' make limited length string in form: + "the string is very lo...(and 15 more)" + ''' + string_len = len(string) + if string_len <= max_width: return string + visible = max_width - 16 - int(log10(string_len)) #expected suffix len "...(and XXXXX more)" + return ''.join((string[:visible], '...(and ', str(string_len - visible), ' more)')) + def shprint(command, *args, **kwargs): '''Runs the command (which should be an sh.Command instance), while logging the output.''' @@ -137,38 +149,67 @@ def shprint(command, *args, **kwargs): kwargs["_out_bufsize"] = 1 kwargs["_err_to_out"] = True kwargs["_bg"] = True + is_critical = kwargs.pop('_critical', False) + tail_n = kwargs.pop('_tail', 0) + filter_in = kwargs.pop('_filter', None) + filter_out = kwargs.pop('_filterout', None) if len(logger.handlers) > 1: logger.removeHandler(logger.handlers[1]) + try: + columns = max(25, int(os.popen('stty size', 'r').read().split()[1])) + except: + columns = 100 command_path = str(command).split('/') command_string = command_path[-1] string = ' '.join(['running', command_string] + list(args)) # If logging is not in DEBUG mode, trim the command if necessary if logger.level > logging.DEBUG: - short_string = string - if len(string) > 100: - short_string = string[:100] + '... (and {} more)'.format(len(string) - 100) - logger.info(short_string + Err_Style.RESET_ALL) + logger.info('{}{}'.format(shorten_string(string, columns - 12), Err_Style.RESET_ALL)) else: - logger.debug(string + Err_Style.RESET_ALL) + logger.debug('{}{}'.format(string, Err_Style.RESET_ALL)) - output = command(*args, **kwargs) need_closing_newline = False - for line in output: - if logger.level > logging.DEBUG: - string = ''.join([Err_Style.RESET_ALL, '\r', ' '*11, 'working ... ', - line[:100].replace('\n', '').rstrip(), ' ...']) - if len(string) < 20: - continue - if len(string) < 120: - string = string + ' '*(120 - len(string)) - sys.stdout.write(string) - sys.stdout.flush() - need_closing_newline = True + try: + msg_hdr = ' working: ' + msg_width = columns - len(msg_hdr) - 1 + output = command(*args, **kwargs) + for line in output: + if logger.level > logging.DEBUG: + msg = line.replace('\n', ' ').replace('\t', ' ').replace('\b', ' ').rstrip() + if msg: +# if len(msg) > msg_width: msg = msg[:(msg_width - 3)] + '...' + sys.stdout.write('{}\r{}{:<{width}}'.format(Err_Style.RESET_ALL, msg_hdr, shorten_string(msg, msg_width), width=msg_width)) + sys.stdout.flush() + need_closing_newline = True + else: + logger.debug(''.join(['\t', line.rstrip()])) + if need_closing_newline: sys.stdout.write('{}\r{:>{width}}\r'.format(Style.RESET_ALL, ' ', width=(columns - 1))) + except sh.ErrorReturnCode, err: + if need_closing_newline: sys.stdout.write('{}\r{:>{width}}\r'.format(Style.RESET_ALL, ' ', width=(columns - 1))) + if tail_n or filter_in or filter_out: + def printtail(out, name, forecolor, tail_n = 0, re_filter_in = None, re_filter_out = None): + lines = out.splitlines() + if re_filter_in is not None: lines = [l for l in lines if re_filter_in.search(l)] + if re_filter_out is not None: lines = [l for l in lines if not re_filter_out.search(l)] + if tail_n == 0 or len(lines) <= tail_n: + info('{}:\n{}\t{}{}'.format(name, forecolor, '\t\n'.join(lines), Fore.RESET)) + else: + info('{} (last {} lines of {}):\n{}\t{}{}'.format(name, tail_n, len(lines), forecolor, '\t\n'.join(lines[-tail_n:]), Fore.RESET)) + printtail(err.stdout, 'STDOUT', Fore.YELLOW, tail_n, + re.compile(filter_in) if filter_in else None, + re.compile(filter_out) if filter_out else None) + printtail(err.stderr, 'STDERR', Fore.RED) + if is_critical: + env = kwargs.get("env") + if env is not None: + info("{}ENV:{}\n{}\n".format(Fore.YELLOW, Fore.RESET, "\n".join("set {}={}".format(n,v) for n,v in env.items()))) + info("{}COMMAND:{}\ncd {} && {} {}\n".format(Fore.YELLOW, Fore.RESET, getcwd(), command, ' '.join(args))) + warning("{}ERROR: {} failed!{}".format(Fore.RED, command, Fore.RESET)) + exit(1) else: - logger.debug(''.join(['\t', line.rstrip()])) - if logger.level > logging.DEBUG and need_closing_newline: - print() + raise + return output # shprint(sh.ls, '-lah') @@ -246,6 +287,17 @@ def current_directory(new_dir): Err_Fore.RESET))) chdir(cur_dir) +@contextlib.contextmanager +def temp_directory(): + temp_dir = mkdtemp() + try: + logger.debug(''.join((Err_Fore.CYAN, ' + temp directory used ', temp_dir, + Err_Fore.RESET))) + yield temp_dir + finally: + shutil.rmtree(temp_dir) + logger.debug(''.join((Err_Fore.CYAN, ' - temp directory deleted ', temp_dir, + Err_Fore.RESET))) def cache_execution(f): @@ -557,6 +609,7 @@ class Context(object): dist_dir = None # the Android project folder where everything ends up libs_dir = None # where Android libs are cached after build but # before being placed in dists + aars_dir = None javaclass_dir = None ccache = None # whether to use ccache cython = None # the cython interpreter name @@ -592,6 +645,12 @@ def javaclass_dir(self): ensure_dir(dir) return dir + @property + def aars_dir(self): + dir = join(self.build_dir, 'aars', self.bootstrap.distribution.name) + ensure_dir(dir) + return dir + @property def python_installs_dir(self): dir = join(self.build_dir, 'python-installs') @@ -1340,6 +1399,71 @@ def get_bootstrap(cls, name, ctx): bootstrap.ctx = ctx return bootstrap + def distribute_libs(self, arch, src_dirs, wildcard='*'): + '''Copy existing arch libs from build dirs to current dist dir.''' + info('Copying libs') + tgt_dir = join('libs', arch.arch) + ensure_dir(tgt_dir) + for src_dir in src_dirs: + for lib in glob.glob(join(src_dir, wildcard)): + shprint(sh.cp, '-a', lib, tgt_dir) + + def distribute_javaclasses(self, javaclass_dir): + '''Copy existing javaclasses from build dir to current dist dir.''' + info('Copying java files') + for filename in glob.glob(javaclass_dir): + shprint(sh.cp, '-a', filename, 'src') + + def distribute_aars(self, arch): + '''Process existing .aar bundles and copy to current dist dir.''' + info('Unpacking aars') + for aar in glob.glob(join(self.ctx.aars_dir, '*.aar')): + self._unpack_aar(aar, arch) + + def _unpack_aar(self, aar, arch): + '''Unpack content of .aar bundle and copy to current dist dir.''' + with temp_directory() as temp_dir: + name = splitext(basename(aar))[0] + jar_name = name + '.jar' + info("unpack {} aar".format(name)) + debug(" from {}".format(aar)) + debug(" to {}".format(temp_dir)) + shprint(sh.unzip, '-o', aar, '-d', temp_dir) + + jar_src = join(temp_dir, 'classes.jar') + jar_tgt = join('libs', jar_name) + debug("copy {} jar".format(name)) + debug(" from {}".format(jar_src)) + debug(" to {}".format(jar_tgt)) + ensure_dir('libs') + shprint(sh.cp, '-a', jar_src, jar_tgt) + + so_src_dir = join(temp_dir, 'jni', arch.arch) + so_tgt_dir = join('libs', arch.arch) + debug("copy {} .so".format(name)) + debug(" from {}".format(so_src_dir)) + debug(" to {}".format(so_tgt_dir)) + ensure_dir(so_tgt_dir) + so_files = glob.glob(join(so_src_dir, '*.so')) + for f in so_files: + shprint(sh.cp, '-a', f, so_tgt_dir) + + def strip_libraries(self, arch): + info('Stripping libraries') + env = arch.get_env() + strip = which('arm-linux-androideabi-strip', env['PATH']) + if strip is None: + warning('Can\'t find strip in PATH...') + return + strip = sh.Command(strip) + filens = shprint(sh.find, join(self.dist_dir, 'private'), join(self.dist_dir, 'libs'), + '-iname', '*.so', _env=env).stdout.decode('utf-8') + logger.info('Stripping libraries in private dir') + for filen in filens.split('\n'): + try: + strip(filen, _env=env) + except sh.ErrorReturnCode_1: + logger.debug('Failed to strip ' + filen) class Recipe(object): url = None @@ -1477,15 +1601,30 @@ def extract_source(self, source, cwd): # print("Unrecognized extension for {}".format(filename)) # raise Exception() - def apply_patch(self, filename): + def apply_patch(self, filename, arch='armeabi'): """ Apply a patch from the current recipe directory into the current build directory. """ info("Applying patch {}".format(filename)) filename = join(self.recipe_dir, filename) - # AND: get_build_dir shouldn't need to hardcode armeabi - sh.patch("-t", "-d", self.get_build_dir('armeabi'), "-p1", "-i", filename) + shprint(sh.patch, "-t", "-d", self.get_build_dir(arch), "-p1", + "-i", filename, _tail=10) + + def apply_all_patches(self, wildcard=join('patches','*.patch'), arch='armeabi'): + patches = glob.glob(join(self.recipe_dir, wildcard)) + if not patches: + warning('requested patches {} not found for {}'.format(wildcard, self.name)) + for filename in sorted(patches): + name = splitext(basename(filename))[0] + patched_flag = join(self.get_build_container_dir(arch), name + '.patched') + if exists(patched_flag): + info('patch {} already applied to {}, skipping'.format(name, self.name)) + else: + self.apply_patch(filename, arch=arch) + sh.touch(patched_flag) + return len(patches) + def copy_file(self, filename, dest): info("Copy {} to {}".format(filename, dest)) @@ -2066,7 +2205,8 @@ def build_cython_components(self, arch): self.ctx.cython, '{}', ';', _env=env) info('ran cython') - shprint(hostpython, 'setup.py', 'build_ext', '-v', _env=env) + shprint(hostpython, 'setup.py', 'build_ext', '-v', _env=env, + _tail=20, _critical=True) print('stripping') build_lib = glob.glob('./build/lib*') @@ -2808,7 +2948,7 @@ def apk(self, args): build = imp.load_source('build', join(dist.dist_dir, 'build.py')) with current_directory(dist.dist_dir): build.parse_args(args) - shprint(sh.ant, 'debug') + shprint(sh.ant, 'debug', _tail=20, _critical=True) # AND: This is very crude, needs improving. Also only works # for debug for now.